CompletableFuture vs Virtual Thread in Java 21

Jacky - Oct 7 '23 - - Dev Community

Some of my knowledge for the first time reading about the features of Java 21, there will be many shortcomings…

With the release of Java 21, virtual threads have been introduced as an alternative concurrency model to completable futures and threads. Both completable futures and virtual threads aim to simplify asynchronous programming in Java, but take different approaches. This article examines the key differences between the two.

What are CompletableFutures?

CompletableFutures in Java

CompletableFuture is a class added in Java 8 that represents an asynchronous computation that can produce a result or complete exceptionally. It implements the Future interface and provides methods for completing the future manually, waiting for completion, registering callbacks, and chaining computable stages together.

Key features of CompletableFuture:

  • Asynchronous operations — does not block the main thread while waiting for a result.
  • Callbacks — can register callbacks to execute when the future completes.
  • Chaining — futures can be chained together to pipeline asynchronous steps.
  • Exception handling — exceptions are handled explicitly via callbacks.
  • Thread pooling — runs computation on ForkJoinPool common thread pool by default.

CompletableFutures enable asynchronous programming by decoupling thread management from application logic. However, they can still encounter problems like callback hell and are not always intuitive to use.

Here is an example usage:



// Run two async tasks
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
  // long running task 
  return "Result1";
});

CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
  // long running task
  return "Result2";  
});

// Join tasks and handle results
future1.thenCombine(future2, (res1, res2) -> {
  return res1 + " " + res2;
}).thenAccept(System.out::println);


Enter fullscreen mode Exit fullscreen mode

CompletableFuture is easy to use and scales well for a moderate number of asynchronous tasks. However, it can incur overhead if creating too many instances.

What are Virtual Threads?

Virtual Threads

Virtual Thread was introduced in Java 14 as an alternative approach for asynchronous programming. The key idea is that instead of managing thread pools explicitly, we can switch virtual threads which are mapped to real threads automatically by the Java runtime.

Key features of virtual threads:

  • Lightweight — low memory and resource overhead compared to OS threads.
  • Managed blocking — blocking ops don’t block OS threads, freeing them for other tasks.
  • Structured concurrency — virtual threads inherit context from parents.
  • Asynchronous — does not require callback-based code. Can use synchronous code style.
  • Optimized scaling — can launch thousands of virtual threads efficiently.

Here is an example:



public class Main {

  public static void main(String[] args) {

    VirtualThread vt = VirtualMachine.getInstance().virtualThread(
      () -> {
        // long running task 
      }
    );

    vt.start();
    // can continue executing main thread logic

  }

}


Enter fullscreen mode Exit fullscreen mode

Virtual threads aim to provide a simpler threading model that scales better and avoids problems like callback hell. The programming model is more like synchronous code, but execution is asynchronous.

Key Differences

Some of the main differences between completable futures and virtual threads:

  • Style — CompletableFuture has a callback-based style while virtual threads allow a synchronous coding style for asynchronous logic.
  • Blocking — Blocking operations on virtual threads don’t consume OS threads like they would with completable futures.
  • Overhead — Virtual threads have lower resource overhead than threads/thread pools used by completable futures.
  • Structued Concurrency — Virtual threads inherit context from parents while completable futures don’t have a built-in concept of parent-child relationships.
  • Chaining — CompletableFuture makes it easier to directly chain multiple future stages together. Chaining logic with virtual threads requires explicit code.
  • Error Handling — Unhandled exceptions on virtual threads terminate the thread while completable futures require handling via callbacks.

In summary:

  • CompletableFuture is high-level, flexible but can incur overhead. Useful for composing many small tasks.
  • Virtual Thread is lightweight and gives structured concurrency. Better fit for fewer long running tasks.

Both have their merits and use cases, but virtual threads represent an evolution in asynchronous programming on the Java platform. The choice between the two depends on the application requirements and programming style preferred.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .