In this post, we look at how to use virtual threads with compatible futures. While it is possible to combine virtual threads and CompletableFuture I also want to show you an alternative way of writing your code.
Using CompletableFuture with Virtual Threads
The easiest way to use virtual threads with CompletableFuture is to pass an ExecutorService as a second parameter to the supplyAsync or runAsync method. In the following example, on lines 2 and 3 you can see the executorService being created and used for the execution of the CompletableFuture.
While it works, is not very pretty code or directly clear to the reader what is going on. That is why I want to show you two alternative ways that make (in my opinion better) use of virtual threads.
Using Future with Virtual Threads
One way to improve the previous example is to use the newVirtualThreadPerTaskExecutor inside a try-with-resource statement. You can use the future returned by the executor later in your code if you need to return a value from a virtual thread.
This example is already a huge improvement in readability over the first example. Making code easier to read is always a great improvement.
Only using Virtual Threads
If you don't need the return value from the virtual thread, you can rewrite the example into something as small as the following example. It does the same, calling the supplier and printing it to the console.
This example is small, concise, and shows the reader exactly what you want the code to do.
CompletableFuture.runAsync().thenRun() to virtual threads
If you are using CompletableFuture to run one method after the previous one is done using the
thenRun method, you can also
switch to virtual threads.
In the following example
bar() will run after
We can replicate this behavior with virtual threads by using the newVirtualThreadPerTaskExecutor and submitting a runnable that encapsulates those two methods. Now foo() and bar() will run like any other sequential code.
These two examples will have the same result, only the last one is easier to read and debug.
In this post, we looked at how to use virtual threads with CompletableFuture, but that it can be more readable if you use virtual threads or the newVirtualThreadPerTaskExecutor instead. Of course, it all depends on your code base, but I hope this offers another perspective on looking at your code.