☕ Java Q66 / 162

What is CompletableFuture in Java?

AI-Powered Answer ✓ Answered

CompletableFuture in Java is a class introduced in Java 8 that provides an asynchronous, non-blocking way to compute a result and execute a callback function once the result is available. It extends the `Future` interface with additional capabilities for composition, error handling, and more flexible asynchronous programming, making it a powerful tool for developing reactive and concurrent applications.

Core Concept

At its core, a CompletableFuture represents a value that will be available at some point in the future. Unlike the traditional Future, which primarily allows checking if a computation is complete and retrieving its result (often blocking until available), CompletableFuture allows you to chain dependent actions, combine results from multiple futures, and handle errors in a non-blocking fashion. It's an excellent choice for orchestrating complex asynchronous workflows.

Key Features and Benefits

  • Asynchronous Task Chaining: Allows you to define a sequence of operations that execute one after another, or in parallel, once a previous task completes, without blocking the main thread.
  • Composition: Provides methods to combine multiple CompletableFutures, for example, waiting for all to complete (allOf) or for the first one to complete (anyOf).
  • Non-Blocking: You attach callbacks (consumer, function) that will be executed when the future completes, instead of blocking the thread to call get() directly.
  • Error Handling: Offers robust mechanisms for handling exceptions that occur during asynchronous computations.
  • Manual Completion: Can be manually completed with a value or an exception, which is useful for integrating with older callback-based APIs or custom asynchronous processes.

CompletableFuture vs. Future

Featurejava.util.concurrent.Futurejava.util.concurrent.CompletableFuture
Composition & ChainingLimited, typically requires blocking or manual polling.Rich API for chaining (thenApply, thenCompose, thenCombine, etc.).
CallbacksNo direct support for callbacks; requires blocking `get()` or polling.Extensive support for non-blocking callbacks.
Error HandlingExceptions are thrown when `get()` is called.Dedicated methods for exception handling (exceptionally, handle, whenComplete).
CompletionCan only be completed by the `Callable`/`Runnable` it's associated with.Can be completed manually using `complete()` or `completeExceptionally()`.
Blocking`get()` method is blocking.Primarily non-blocking; `get()` is available but typically avoided.

Basic Usage Example

Here's a simple example demonstrating how to run an asynchronous task and apply a transformation using CompletableFuture.

java
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;

public class CompletableFutureExample {
    public static void main(String[] args) {
        System.out.println("Main thread started.");

        // Create a CompletableFuture that runs asynchronously
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("Task 1: Running in " + Thread.currentThread().getName());
            try {
                Thread.sleep(1000); // Simulate a long-running operation
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            return "Hello";
        });

        // Chain another asynchronous task to transform the result
        CompletableFuture<String> greetingFuture = future.thenApplyAsync(name -> {
            System.out.println("Task 2: Running in " + Thread.currentThread().getName());
            return name + " World!";
        });

        // Block and get the final result (in real applications, avoid blocking)
        try {
            String result = greetingFuture.get(); // Blocks until the result is available
            System.out.println("Result: " + result);
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println("Main thread finished.");

        // In a real application, you might shut down the executor if it's custom.
        // Default ForkJoinPool.commonPool() is used by default for supplyAsync, 
        // but for specific needs, you'd use CompletableFuture.supplyAsync(supplier, executor).
    }
}

Common Methods

  • supplyAsync(Supplier<U> supplier): Runs a Supplier asynchronously and returns its result.
  • runAsync(Runnable runnable): Runs a Runnable asynchronously.
  • thenApply(Function<? super T,? extends U> fn): Returns a new CompletableFuture that, when this future completes normally, is executed with this future's result as the argument to the supplied function.
  • thenAccept(Consumer<? super T> action): Returns a new CompletableFuture that, when this future completes normally, is executed with this future's result as the argument to the supplied action.
  • thenRun(Runnable action): Returns a new CompletableFuture that, when this future completes normally, executes the given action.
  • thenCompose(Function<? super T, ? extends CompletionStage<U>> fn): FlatMap equivalent. Returns a new CompletableFuture that is completed with the result of the CompletionStage returned by the supplied function.
  • thenCombine(CompletionStage<? extends U> other, BiFunction<? super T, ? super U, ? extends V> fn): Combines the results of two CompletableFutures when both complete.
  • allOf(CompletableFuture<?>... cfs): Returns a new CompletableFuture that is completed when all of the given CompletableFutures complete.
  • anyOf(CompletableFuture<?>... cfs): Returns a new CompletableFuture that is completed when any of the given CompletableFutures complete.
  • exceptionally(Function<Throwable, ? extends T> fn): Returns a new CompletableFuture that, when this future completes exceptionally, is completed with the result of the supplied function applied to the exception.
  • handle(BiFunction<? super T, Throwable, ? extends U> fn): Returns a new CompletableFuture that, when this future completes (normally or exceptionally), is completed with the result of the supplied function applied to the outcome.