Concurrencyhigh

CompletableFuture

CompletableFuture represents an async computation. It can be manually completed (complete(), completeExceptionally()) or created from async operations (supplyAsync(), runAsync()). It supports chaining via thenApply(), thenCompose(), thenCombine().

Memory anchor

CompletableFuture = a promise note chain. thenApply = 'when pizza arrives, add cheese.' thenCompose = 'when pizza arrives, order dessert too.' Errors vanish into a black hole unless you attach an exceptionally() safety net.

Expected depth

thenApply() transforms the result (like map). thenCompose() chains another CompletableFuture (like flatMap — avoids nested CompletableFuture<CompletableFuture<T>>). thenCombine() merges two independent futures. exceptionally() handles errors. allOf() / anyOf() combine multiple futures. Without an explicit executor, callbacks run on the ForkJoinPool.commonPool() — avoid blocking operations in callbacks. thenApplyAsync() dispatches the callback to an executor thread, preventing callback chains from blocking the upstream thread.

Deep — senior internals

CompletableFuture's error model: each stage can fail independently; exceptionally() recovers; handle() handles both success and failure. join() is like get() but throws CompletionException (unchecked) instead of ExecutionException. Cancellation propagates only forward (downstream) not backward (upstream) — cancelling a derived future does not cancel the original computation. For structured async code in Java 21+, virtual threads combined with blocking code are often simpler and more readable than deeply nested CompletableFuture chains. Project Loom's structured concurrency (StructuredTaskScope) provides deterministic cancellation semantics.

🎤Interview-ready answer

CompletableFuture enables non-blocking async pipelines: thenApply() transforms results, thenCompose() chains async steps (avoiding nested futures), exceptionally() or handle() manages errors. The critical operational gotcha: without specifying an executor, callbacks run on the ForkJoinPool common pool — blocking inside a callback starves the pool for other tasks. Always pass an explicit executor to *Async methods in production code and handle exceptions explicitly, as unhandled CompletableFuture exceptions are silently swallowed.

Common trap

Unhandled exceptions in CompletableFuture stages are silently swallowed unless you call get()/join() or attach an exceptionally()/whenComplete() handler — the exception disappears and the future stays permanently incomplete.