1. Introduction

Kotlin’s coroutines allow us to have asynchronous programming in a sequential code style. Coroutines are built on top of suspend functions, which can be paused and resumed without blocking the thread. In this article, we’re going to explore how to call Kotlin suspend functions from Java, along with some things to be aware of while doing so.

2. Suspend Functions and Java

Let’s start with a suspend function as an example. Our example function returns “Welcome” after a 10ms delay:

suspend fun checkIn(): String {
    delay(10)
    return "Welcome"
}
public static final Object checkIn(@NotNull Continuation var0) {
    ...
}

So, we clearly see the suspend function is converted to a static method with a Continuation as the last argument to follow the CPS style.

Kotlin has native support for the Continuation, which means the Kotlin compiler manages the Continuation and passes it between functions that are marked with suspend.

After the suspend function is done, the generated code calls the resume() or resumeWithException() extension of the Continuation, which changes the state of the coroutine from suspended back to running. These extension functions provide an easy way to call resumeWith() with a Result, which can either be a success value or the exception that caused the suspend function to fail:

public inline fun <T> Continuation<T>.resume(value: T): Unit =
    resumeWith(Result.success(value))

public inline fun <T> Continuation<T>.resumeWithException(exception: Throwable): Unit =
    resumeWith(Result.failure(exception))

If we take a look at BaseContinuationImpl, it shows the complex logic for handling the suspension points and the final top-level result of a suspend function.

3. Implement a Continuation in Java

Comparing our checkIn() function in Kotlin and its decompiled Java code shows that Kotlin’s code is much simpler, as the Kotlin compiler manages the complexity and it reflects in the generated Java code. If we need to call a Kotlin suspend function from Java, we have to provide the Continuation object from our Java code, but the Kotlin implementation of Continuation is internal, and we can’t access it from Java.

Let’s start with implementing a Continuation to control the execution flow:

class CustomContinuation<T> implements Continuation<T> {

    @Override
    public void resumeWith(@NotNull Object o) {
        if (o instanceof Result.Failure)
            consumeException((((Result.Failure) o).exception));
        else
            consumeResult((T)o);
    }

    @NotNull
    @Override
    public CoroutineContext getContext() {
        return EmptyCoroutineContext.INSTANCE;
    }
}

The consumeException() and consumeResult() methods are placeholders of the real logic. This simplified implementation allows us to call a suspend function, but not to coordinate and combine a group of suspend functions in our Java code. In other words, with this piece of code, we only handle the top-level completion of the suspend function and manage the result.

3.1. Use CustomContinuation to Call a Suspend Function

Let’s continue to use our Continuation to call the checkIn() function. First, we customize CustomContinuation to pass the return value or exception to a CompletableFuture:

class CustomContinuation<T> implements Continuation<T> {
    private final CompletableFuture<T> future;

    public CustomContinuation(CompletableFuture<T> future) {
        this.future = future;
    }

    @Override
    public void resumeWith(@NotNull Object o) {
        if (o instanceof Result.Failure)
            future.completeExceptionally(((Result.Failure) o).exception);
        else
            future.complete((T) o);
    }
    .....
}

Then, we use it to call the checkIn() function:

CompletableFuture<String> suspendResult = new CompletableFuture<>();
checkIn(new CustomContinuation<>(suspendResult));

Assertions.assertEquals(WELCOME, suspendResult.get());

3.2. Use CustomContinuation to Handle Exceptions

Now, let’s see what happens when the suspend function throws an exception. Let’s introduce a new suspend function to throw an exception after a delay:

suspend fun checkInClosed(): String {
    delay(10)
    throw Exception("Sorry! We are closed.")
}

Then, we call this suspend function from our Java code:

CompletableFuture<String> suspendResult = new CompletableFuture<>();
checkInClosed(new CustomContinuation<>(suspendResult));

Assertions.assertThrows(Exception.class, suspendResult::get);

4. Coroutine Extensions in Java

Kotlin provides some extensions to use with coroutines. *These extensions allow us to wrap the suspend functions in other types that are easier to use with other languages and libraries, like Future, Single, and Mono, among others*. To be able to use them, we have to include the suitable coroutines library in the project dependencies.

4.1. Blocking Call

Let’s start with BuildersKt.runBlocking(), which is useful to make a blocking call to the Kotlin suspend function and get the result. BuildersKt is available in org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm. After we’ve added the dependency, we call checkIn():

String result = BuildersKt.runBlocking(
  EmptyCoroutineContext.INSTANCE,
  (scope, continuation) -> checkIn(continuation)
);
Assertions.assertEquals(WELCOME, result);

The BuildersKt.runBlocking() function has two parameters, an instance of CoroutineContext and a lambda that we will call our suspend function from.

For the first argument, we get an empty context by EmptyCoroutineContext.INSTANCE. CoroutineContext is an indexed set from CoroutineContext.Key to CoroutineContext.Element. The indexed set means that CoroutineContext is a mix between a set and a map, so we can look up a CoroutineContext.Key and get the associated CoroutineContext.Element. A Coroutine saves the information that it uses locally in its CoroutineContext.

Now, let’s review a few of the other available extensions.

4.2. Convert to Future

When we don’t want to block the thread, we can get a Future from the suspend call with help of FutureKt.future(). It’s available in org.jetbrains.kotlinx:kotlinx-coroutines-jdk8 . The signature of FutureKt.future() in Java is:

public static CompletableFuture future(
    @NotNull CoroutineScope scope,
    @NotNull CoroutineContext context,
    @NotNull CoroutineStart start,
    @NotNull Function2 block
)

Let’s call our checkIn():

CompletableFuture<String> suspendResult = FutureKt.future(
  CoroutineScopeKt.CoroutineScope(EmptyCoroutineContext.INSTANCE),
  EmptyCoroutineContext.INSTANCE,
  CoroutineStart.DEFAULT,
  (scope, continuation) -> checkIn(continuation)
);
Assertions.assertEquals(WELCOME, suspendResult.get());

To provide an instance of CoroutineScope, we used CoroutineScopeKt.CoroutineScope. We’ll use an empty context for the context again. We set CoroutineStart to Default, which means that we schedule the coroutine for immediate execution within its context. Finally, the last parameter is a lambda where we call our suspend function.

4.3. Using Suspend Functions with Reactive Extensions (Rx)

If we use Rx in our project, we can use helper libraries for Rx. Several libraries are available to choose from based on the project Rx version, like org.jetbrains.kotlinx:kotlinx-coroutines-rx2 for Rx2 and org.jetbrains.kotlinx:kotlinx-coroutines-rx3 for Rx3. These libraries allow us to get the result of the suspend function as a Single or Flowable.

Here, we see how to get a Single from a suspend function in our Java code:

Single<String> suspendResult = RxSingleKt.rxSingle(
  EmptyCoroutineContext.INSTANCE, 
  (scope, continuation) -> checkIn(continuation)
);
Assertions.assertEquals(WELCOME, suspendResult.blockingGet());

4.4. Using suspend Functions with Reactor

There’s also a Project Reactor extension available from org.jetbrains.kotlinx:kotlinx-coroutines-reactor that allows us to get an instance of Mono or Flux from suspend functions:

Mono<String> suspendResult = MonoKt.mono(
  EmptyCoroutineContext.INSTANCE, 
  (scope, continuation) -> checkIn(continuation)
);
Assertions.assertEquals(WELCOME, suspendResult.block());

5. Coroutine Extensions in Kotlin

Looking at our previous approaches, we can see that calling a suspend function from Java is not straightforward enough, even using the helper libraries. The final code is complex and needs to provide Coroutines’ domain objects like CoroutineContext and CoroutineStart.

To remove this complexity from the Java code, we can change our approach. This time, instead of using the helpers in Java code, we can use them in the Kotlin code to wrap the suspend function.

Let’s see how we can wrap our suspend function to return an instance of CompletableFuture:

fun checkInAsync(): CompletableFuture<String> = GlobalScope.future { checkIn() }

The GlobalScope executes the suspend function as a top-level background process. While this isn’t suitable in all situations, it works for this example. However, when implementing this code, IntelliJ suggests adding @DelicateCoroutinesApi to the method so that developers are informed this method is for a limited use case and should be used carefully.

Then, we use the wrapped Kotlin function easily in our Java code without any boilerplate code:

CompletableFuture<String> suspendResult = checkInAsync();
Assertions.assertEquals(WELCOME, suspendResult.get());

6. Conclusion

In this article, we looked at different approaches to calling a Kotlin suspend function from Java. We started with a custom Continuation implementation, then we looked at some varieties of kotlinx-coroutines libraries. Finally, after we understood the complexity of calling a suspend function from Java, we discussed the approach of using kotlinx-coroutines in Kotlin to wrap the suspend function in more Java-friendly types.

As always, the code of the examples is available over on GitHub.