Call Suspending Kotlin Code from Java
Five ways to connect new coroutines with old codebases
Without the suspend
keyword, Java can’t call a suspending function directly. But Kotlin’s suspension points and coroutines are based on the same universal async principles as callbacks and futures. I’ll show you five easy ways to convert from one to the other, so you can write suspending code in Kotlin and call it from Java.
Need help choosing? Look at whether the coroutine is launched eagerly by the Kotlin producer (hot 🔥) or executed on demand by the Java consumer (cold ❄️). Hot coroutines usually need a scope to manage their errors and cancellation.
1. Start a coroutine, send a callback
This one’s great if your Kotlin code is part of a component that already has its own CoroutineScope
to manage errors and resources.
fun callThisFromJava(callback: Consumer<MyResult>) {
myScope.launch { callback.accept(mySuspendingTask()) }
}
Hot 🔥: immediately starts a new coroutine to call mySuspendingTask()
. When it’s done, the coroutine delivers its result via a basic Java-friendly callback.
Errors and cancellation must be handled on the producer side, via myScope
—the Java code doesn’t see anything except the successful result.
2. Put it in a CompletableFuture
fun callThisFromJava(): CompletableFuture<MyResult> {
return GlobalScope.future { mySuspendingTask() }
}
Hot 🔥: immediately starts a new coroutine to call mySuspendingTask().
Uses a CompletableFuture
to give Java not just the result, but also the responsibility for errors and cancellation.
As long as the Java code actually remembers to handle the running task and its outcome, it’s safe to use GlobalScope
to ignore those things on the Kotlin side.
3. Hand over control with a Mono/Single
fun callThisFromJava(): Mono<MyResult> {
return mono { mySuspendingTask() }
}
Cold ❄️: creates a Project Reactor Mono
, but doesn’t run anything until something subscribes to it. That means the Java caller becomes completely responsible for the suspending code’s execution, including errors and cancellation, and there’s no need for any coroutine scope.
Add kotlinx-coroutines-reactor
to your dependencies for mono { … }
, or kotlinx-coroutines-rx2
/-rx3
for RxJava via rxSingle { … }
.
4. Turn a Flow
into a Publisher
This one’s great if you’ve already got a stream of multiple values in a Flow
—or you can just use publish { … }
to skip the flow-creation step.
fun callThisFromJava(): Publisher<MyResult> {
return flow { emit(mySuspendingTask()) }.asPublisher()
}
Cold ❄️: returns a Reactive Streams Publisher
, but doesn’t run anything until something subscribes to it. Once again, there’s no need for a coroutine scope, since the Java subscriber is responsible for executing the code and dealing with errors and cancellation.
Add kotlinx-coroutines-reactive
to your dependencies, plus your chosen Reactive Streams implementation.
5. Block the thread, carefully
fun callThisFromJava(): MyResult {
return runBlocking { mySuspendingTask() }
}
Immediately executes mySuspendingTask()
in a new coroutine, blocking the current thread to host the coroutine and wait for its result. The hot/cold distinction doesn’t really apply here, since we’re no longer treating the task as asynchronous.
Be careful! It’s only safe to use runBlocking { … }
when you can guarantee that the caller of callThisFromJava()
isn’t already running in a coroutine.
My upcoming book, Kotlin Coroutine Confidence, comes with a whole chapter on how to wire up suspending functions with callbacks and futures in Java and elsewhere. Grab your early access beta copy today to be part of the book’s development and get a head start on untangling your async!