CompletableFuture
CompletableFuture (Java 8) represents an asynchronous result that you can compose without blocking.
- Start:
supplyAsync(supplier, executor),runAsync - Transform:
thenApply(like map),thenCompose(like flatMap, for dependent async calls) - Combine:
thenCombine(two futures),allOf,anyOf - Errors:
exceptionally,handle,whenComplete - Timeouts (Java 9):
orTimeout,completeOnTimeout
It's ideal for calling several services in parallel and merging the results.
Example
ExecutorService io = Executors.newFixedThreadPool(8);
CompletableFuture<User> user =
CompletableFuture.supplyAsync(() -> userApi.get(id), io);
CompletableFuture<List<Course>> courses =
CompletableFuture.supplyAsync(() -> courseApi.forUser(id), io);
CompletableFuture<Dashboard> dashboard = user
.thenCombine(courses, Dashboard::new)
.orTimeout(2, TimeUnit.SECONDS) // Java 9+
.exceptionally(ex -> Dashboard.fallback(id));
Dashboard d = dashboard.join();Common mistake
Using thenApply when your function itself returns a CompletableFuture. You end up with CompletableFuture<CompletableFuture<T>>; use thenCompose instead.
Under the hood
Without an explicit executor, the async methods use ForkJoinPool.commonPool(), which the whole JVM shares; blocking I/O there starves parallel streams and other tasks. With virtual threads (Java 21), plain blocking code in Executors.newVirtualThreadPerTaskExecutor() is often clearer than long CompletableFuture chains.
Check yourself
Which method waits for all of several futures?
How this connects
Know these first
Where this leads
You've reached the end of this thread. Try a learning path for what's next.
Part of Crack the Java interview, Microservices and production.
Was this lesson helpful?
Finished reading? Mark it complete to track your progress.