Skip to main content
  1. Java Concurrency (java.util.concurrent)/

CompletableFuture

2 mins

CompletableFuture implements both the Future and CompletionStage interfaces, allowing you to build complex asynchronous pipelines with functional composition.

Source Code #

View Source on GitHub

The CompletionStage Interface #

A CompletionStage represents a “stage” of a task. You can chain stages together to define what should happen after a task is finished, regardless of whether it succeeds or fails.

public interface CompletionStage<T> {
    <U> CompletionStage<U> thenApply(Function<? super T,? extends U> fn);
    CompletionStage<Void> thenAccept(Consumer<? super T> action);
    CompletionStage<T> exceptionally(Function<Throwable, ? extends T> fn);
    // ... 40+ methods for chaining ...
}

Canonical Usage #

When to use: Use CompletableFuture whenever you want to perform asynchronous work and chain its results into a pipeline (e.g., fetch data from a database, then call an external API, then log the result) without blocking the main thread.

Common Patterns:

  • Async Supply: Using supplyAsync(supplier, executor) to start an async task.
  • Functional Chaining: Using thenApply, thenCompose, and thenCombine to build task graphs.
  • Error Handling: Using exceptionally or handle for robust async error recovery.
CompletableFuture.supplyAsync(() -> userService.getUser(id), executor)
    .thenApply(user -> orderMapper.toOrder(user))
    .thenCompose(order -> paymentService.processOrder(order))
    .thenAccept(receipt -> logger.info("Payment complete: " + receipt))
    .exceptionally(ex -> {
        logger.error("Payment failed", ex);
        return null;
    });

Execution Strategies #

  • Non-Async Methods: thenApply(fn) usually runs in the same thread that completed the previous stage.
  • Async Methods: thenApplyAsync(fn) runs the function in a separate thread (by default, ForkJoinPool.commonPool()).