What is a Future, and how does CompletableFuture improve on it?

9 minadvancedfuturecompletablefutureasync

Quick Answer

Future<V> represents the result of an asynchronous computation; you can check isDone(), cancel it, or call the blocking get() to retrieve the result once ready, but plain Future offers no way to chain follow-up actions, combine multiple futures, or register a non-blocking callback. CompletableFuture (Java 8+) implements Future but adds a rich, composable API — thenApply, thenCompose, thenCombine, exceptionally, allOf — for building non-blocking asynchronous pipelines.

Detailed Answer

Future<V> (Java 5+) represents a value that will be available later, from an asynchronous computation submitted to an ExecutorService:

Future<Integer> future = executor.submit(() -> compute());
future.isDone();      // check without blocking
int result = future.get(); // blocks until the result is ready (or throws)
future.cancel(true);   // attempt to cancel

Its big limitation: there's no way to react to completion without blocking — no callback registration, no chaining a follow-up computation, no combining two futures' results. You either poll isDone() in a loop or block on get().

CompletableFuture<T> (Java 8+) implements Future but adds a rich, functional, non-blocking composition API:

CompletableFuture<Integer> cf = CompletableFuture
    .supplyAsync(() -> fetchUserId())
    .thenApply(id -> id * 2)                       // transform the result
    .thenCompose(id -> fetchProfileAsync(id))      // chain another async call
    .exceptionally(ex -> { log(ex); return null; }); // handle failure

CompletableFuture<Void> both = CompletableFuture.allOf(cf1, cf2, cf3); // wait for several
CompletableFuture<Integer> combined = cf1.thenCombine(cf2, (a, b) -> a + b);

Key additions over plain Future:

  • thenApply/thenAccept/thenRun: chain transformations without blocking.
  • thenCompose: flatten a future-returning function (avoids Future<Future<T>>).
  • thenCombine/allOf/anyOf: combine results from multiple independent futures.
  • exceptionally/handle: structured error handling in the pipeline itself, instead of a try/catch around a blocking get().
  • Manual completion: complete(value) lets non-executor code (e.g., a callback-based API) resolve the future directly.

CompletableFuture is the standard building block for non-blocking async pipelines in modern Java, similar in spirit to Promises in JavaScript.

Related Resources