A task that produces a result needs a handle to fetch that result later. Callable<V> is the task, Future<V> is the handle, and CompletableFuture<V> is the handle that grew up — turning blocking polling into a composable, callback-driven pipeline of asynchronous stages.
From Callable to Future
Runnable runs and returns nothing; Callable<V> runs and returns a V (and may throw a checked exception). When you submit either to an ExecutorService, you get back a Future<V> — a placeholder for a result that does not exist yet. The catch is that Future is pull-based: the only way to get the value out is get(), which blocks the calling thread until the task finishes. You cannot attach a continuation, you cannot combine two futures, and you cannot complete one from the outside. isDone() polling is the workaround, and it is a bad one.
CompletableFuture: push, don't pull
CompletableFuture<V> (Java 8+) implements both Future and CompletionStage. Instead of blocking, you declare what happens next and let the framework run it when the value arrives. That is the whole shift: from "ask me later" to "call me back".
Future: submit ──► [task] ──► get() BLOCKS here ──► value
CompletableFuture: supplyAsync ──► [stage1] ──► thenApply ──► [stage2]
(no thread blocks; each stage
fires when the prior completes)The core building blocks:
thenApply/thenAccept/thenRun— transform, consume, or just react to a result.thenCompose— flat-map: chain a stage that itself returns aCompletableFuture, avoiding nested futures.thenCombine— join two independent futures into one result.*Asyncvariants — run the callback on a supplied (or common) pool instead of the completing thread.exceptionally/handle/whenComplete— recover from or observe failures.allOf/anyOf— fan-in over many futures.
Why interviewers reach for it
This topic is where concurrency stops being about locks and starts being about orchestration: composing many async calls (service A, then B with A's result, plus C in parallel), handling partial failure, and never blocking a request thread. It is the bridge to reactive thinking and a daily reality in any non-trivial backend.