Simplify Java async programming with virtual threads using an async/await style API.
The Async
class provides utility methods for executing code asynchronously on virtual threads. It simplifies handling of asynchronous operations and exceptions and provides familiar API.
-
await(() -> {/* blocking code */})
: Executes a code block on a virtual thread. Handles checked exceptions and wraps them intoRuntimeException
.RuntimeException
andError
are thrown as it is. If any other unexpectedThrowable
occurs, it throws anIllegalStateException
. -
await(Callable<T> block)
: Executes aCallable<T>
block on a virtual thread and returns the result. HandlesInterruptedException
,ExecutionException
, and other general exceptions by wrapping them in aRuntimeException
. -
await(Future<T> future)
: Waits for theFuture<T>
to complete and returns its result. Internally callsawait(Callable<T> block)
. -
await(CompletableFuture<T> completableFuture)
: Waits for theCompletableFuture<T>
to complete and returns its result. Internally callsawait(Callable<T> block)
.
See Sample.java.
Project Loom has introduced Virtual Threads, but the API requires some boilerplate code to use it effectively in real-life projects:
To run blocking code in a Virtual Thread, it should be wrapped in:
Thread.ofVirtual().start(() -> {
// run some blocking code here
}).join()
When you need to get the execution result back, a common approach is to run it in an executor:
int returnFromVirtualThread() {
try (final var executor = Executors.newVirtualThreadPerTaskExecutor()) {
final var task = executor.submit(() -> {
// Do some expensive calculation here
return 42; // Return result
});
return task.get(); // Get result from task
} catch (ExecutionException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}
What if it were possible to use syntax similar to Javascript's async/await
style?
This library introduces helpful utilities to simplify calls in Virtual Threads:
For example, to call a lambda function, even throwing exceptions, use:
import me.kpavlov.await4j.Async.await;
...
final var completed = new AtomicBoolean();
final var slowCalculation = () -> {
// Do something slow in a virtual thread
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// Set flag to "true" to indicate, that the task is completed
completed.set(true);
};
await(slowCalculation);
// Verify that calculation has been completed
System.out.println("Completed: " + completed.get()); // "Completed: true"
To call a lambda that returns a value (Callable), use:
import me.kpavlov.await4j.Async.await;
...
final var result = await(() -> {
// Do some expensive calculation here
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// Return the result
return 42;
});
System.out.println("Result: " + result); // "Result: 42"
Lambdas may throw exceptions, unlike the java.lang.Runnable
interface. All non-runtime exceptions are wrapped in java.lang.RuntimeException
, and java.lang.Error
will be re-thrown.
Using java.util.concurrent.CompletableFuture
and java.util.concurrentFuture
from the Java API is also simplified:
final CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
// Do some expensive calculation here
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// Return the result
return 42;
});
final var completableFutureResult = await(completableFuture);
System.out.println("CompletableFuture result: " + completableFutureResult); // "Result: 42"
final var futureResult = await((Future<Integer>) completableFuture);
System.out.println("Future Result: " + futureResult); // "Result: 42"
See full list of requirements here.
- Result<T> - A discriminated union that encapsulates a successful outcome with a value of type T or a failure with an arbitrary Throwable exception. Similar to Result in Kotlin.
- ThrowingRunnable - Runnable, which can throw Exception
If you want to write better code on JVM, use Kotlin Coroutines. This library remains a simpler choice for Java projects where adopting or migrating to Kotlin is not feasible.
The library focuses on running blocking code on Virtual Threads without providing additional parallelism optimizations. If your IO operations are slow, they will not run faster. If a lambda takes one second to run, await(...)
will also take approximately one second, but on a virtual thread.
-
Add project dependency. Latest version can be found on maven central repository:
Maven:
<!-- https://mvnrepository.com/artifact/me.kpavlov.await4j/await4j --> <dependency> <groupId>me.kpavlov.await4j</groupId> <artifactId>await4j</artifactId> <version>[LATEST]</version> </dependency>
Gradle:
// https://mvnrepository.com/artifact/me.kpavlov.await4j/await4j implementation("me.kpavlov.await4j:await4j:${await4jVersion}")
-
Import methods from Async
import me.kpavlov.await4j.Async.await;
- JEP 444: Virtual Threads -- Virtual threads are lightweight threads that dramatically reduce the effort of writing, maintaining, and observing high-throughput concurrent applications.
- JEP 480: Structured Concurrency -- Simplify concurrent programming by introducing an API for structured concurrency. Structured concurrency treats groups of related tasks running in different threads as a single unit of work, thereby streamlining error handling and cancellation, improving reliability, and enhancing observability. This is a preview API.
- JEP 429: ScopedValues -- Scoped values enables sharing of immutable data within and across threads. They are preferred to thread-local variables, especially when using large numbers of virtual threads. This is an incubating API.
- Java Async-Await -- Async-Await support for Java CompletionStage.