Callable, Future & CompletableFuture — Java Interview Guide | Cracked Java
Senior

Callable, Future & CompletableFuture

Returning results from tasks and composing asynchronous pipelines: Future limitations, thenApply/thenCompose/thenCombine, the *Async variants, exception handling, and allOf/anyOf.

Prereqs: thread-pools

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)
Future (blocking pull) vs CompletableFuture (callback push)

The core building blocks:

  • thenApply / thenAccept / thenRun — transform, consume, or just react to a result.
  • thenCompose — flat-map: chain a stage that itself returns a CompletableFuture, avoiding nested futures.
  • thenCombine — join two independent futures into one result.
  • *Async variants — 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.

Questions

6 in this topic