Java CompletableFuture for async non-blocking code
CompletableFuture, supplyAsync, thenApply, thenAccept, thenCompose, exceptionally, allOf, anyOf, async pipeline, ForkJoinPool, non-blocking chaining
CompletableFuture
CompletableFuture enables non-blocking asynchronous pipelines. Instead of blocking on Future.get(), you chain callbacks that execute when each stage completes, freeing the calling thread immediately.
Async Pipeline
import java.util.concurrent.CompletableFuture;
CompletableFuture
.supplyAsync(() -> fetchUserFromDB(42)) // runs on ForkJoinPool
.thenApply(user -> user.getEmail().toLowerCase())
.thenApply(email -> "Hello, " + email)
.thenAccept(System.out::println)
.exceptionally(ex -> {
System.err.println("Failed: " + ex.getMessage());
return null;
});
Combining Multiple Futures
CompletableFuture f1 = CompletableFuture.supplyAsync(() -> "Result A");
CompletableFuture f2 = CompletableFuture.supplyAsync(() -> "Result B");
CompletableFuture.allOf(f1, f2)
.thenRun(() -> System.out.println("Both done: " + f1.join() + ", " + f2.join()));
thenCompose — Flat Map for Futures
CompletableFuture future = CompletableFuture
.supplyAsync(() -> getUserId())
.thenCompose(id -> fetchOrdersAsync(id)); // avoids CF> nesting
thenApply transforms the result synchronously (like map). thenCompose chains another async operation whose result is a CompletableFuture (like flatMap) — it flattens the nesting. Without thenCompose, chaining async operations produces CompletableFuture<CompletableFuture<T>>.
exceptionally handles failures at any stage. allOf waits for all futures; anyOf completes when the first one does.
To run a CompletableFuture on a specific thread pool instead of the default ForkJoinPool, pass your ExecutorService as the second argument: CompletableFuture.supplyAsync(() -> work(), myPool). This is important for I/O-bound async work where the common pool should not be blocked.
