Back to CompletableFuture & Resilience
Q9Question 9 of 19

CompletableFuture vs Future?

Key Concepts
  • Future (Java 5) provides only blocking get(), polling isDone(), and cancellation, with no callbacks or chaining.
  • CompletableFuture (Java 8) adds non-blocking callbacks, chaining, composition, error handling, and manual completion.
  • Future forces the caller to block or poll; CF lets you register callbacks that fire automatically when the result is ready.
  • CF provides allOf() and anyOf() for combining multiple futures, which Future cannot do.
  • CF supports manual completion with complete() and completeExceptionally() for testing and integration.
  • CF is a superset: it implements Future and adds the full async programming model.

Definition

Future provides only blocking get() and polling isDone(), while CompletableFuture adds non-blocking callbacks, chaining with thenApply/thenCompose, composition with thenCombine/allOf/anyOf, error handling with exceptionally/handle, and manual completion.
  • Future (Java 5) – a handle to a pending result; limited to get() (blocking), isDone() (polling), and cancel() (cancellation).
  • CompletableFuture (Java 8) – a complete async programming model; implements Future and adds callbacks, chaining, composition, error recovery, and manual completion.
  • Blocking vs non-blocking – Future forces you to block or poll; CF registers callbacks that fire automatically.
  • Chaining – Future has none; CF has thenApply, thenCompose, thenAccept.
  • Composition – Future cannot combine futures; CF has thenCombine, allOf, anyOf.
  • Manual completion – Future cannot be completed from outside; CF has complete() and completeExceptionally().

Future is like submitting a form at National Bank of India and getting a token number. All you can do is wait at the counter until they call your number (blocking get()). You cannot say "when ready, call me." CompletableFuture is a smart pager. It vibrates when ready (callback). You can chain instructions: "when ready, calculate tax, then send receipt." You can combine: "when both balance and score are ready, decide the loan." You do not have to stand at the counter.

Understanding the Concepts

What can Future do?

Future (Java 5) provides five operations and nothing else.

JAVA
ExecutorService pool = Executors.newFixedThreadPool(4);
Future<String> future = pool.submit(() -> settleTrade("TRD-001"));

future.get();                         // blocks until done
future.get(5, TimeUnit.SECONDS);     // blocks with timeout
future.isDone();                      // poll: done yet?
future.cancel(true);                  // attempt cancel
future.isCancelled();                 // was it cancelled?

// Cannot do:
// future.thenApply(...)        NO chaining
// future.thenCombine(other)    NO composition
// future.exceptionally(...)    NO error callbacks
// future.complete("manual")    NO manual completion

What does CompletableFuture add?

JAVA
CompletableFuture<String> cf = CompletableFuture
    .supplyAsync(() -> settleTrade("TRD-001"))
    .thenApply(result -> "Confirmed: " + result)         // chain!
    .thenCombine(fetchTaxAsync(), (r, tax) -> r + tax)   // combine!
    .exceptionally(ex -> "Failed: " + ex.getMessage())   // error!
    .whenComplete((r, ex) -> log(r, ex));                // observe!

// Manual completion:
CompletableFuture<String> manual = new CompletableFuture<>();
manual.complete("Manually set!");

How do they compare?

| Feature | Future (Java 5) | CompletableFuture (Java 8) |
|---|---|---|
| Get result | get() blocks | get()/join() + non-blocking callbacks |
| Chain | No | thenApply, thenCompose |
| Combine | No | thenCombine, allOf, anyOf |
| Error handling | No callbacks | exceptionally, handle, whenComplete |
| Manual completion | No | complete(), completeExceptionally() |
| Async variants | No | thenApplyAsync, supplyAsync |
| Non-blocking | No (block or poll) | Yes (callbacks fire automatically) |
| Creation | pool.submit() | supplyAsync(), runAsync(), new CF() |
| Timeout (Java 9) | No | orTimeout, completeOnTimeout |

Why is non-blocking important?

With Future, the only way to use the result is to block the calling thread. With CF, you register a callback that fires when the result is ready, and the calling thread is free to do other work.

JAVA
// Future: caller blocked
Future<String> f = pool.submit(() -> fetchBalance());
// ... caller cannot do anything useful here ...
String balance = f.get(); // BLOCKED until done

// CF: caller free
CompletableFuture.supplyAsync(() -> fetchBalance())
    .thenApply(b -> calculateInterest(b))
    .thenAccept(i -> sendStatement(i));
// Caller is free! Pipeline runs in the background.

When should you still use Future?

Future is simpler when you genuinely just need to submit a task and block for the result. For simple fire-and-submit patterns with ExecutorService.submit(), plain Future is sufficient. When you need chaining, composition, or non-blocking behavior, use CompletableFuture.

What about manual completion?

CompletableFuture can be completed from outside the executing thread. This is useful for testing, integration with callback-based APIs, and bridging different async models.

JAVA
CompletableFuture<String> cf = new CompletableFuture<>();

// Some callback-based API:
callbackApi.fetchBalance("SAV2001", result -> {
    cf.complete(result); // complete from callback
});

// Or complete with error:
cf.completeExceptionally(new RuntimeException("Timeout"));

What are common interview follow-up questions?

  • Does CompletableFuture extend Future? Yes, it implements both Future and CompletionStage.
  • Can you use CompletableFuture with ExecutorService.submit()? Not directly; submit() returns Future, not CF. Use CompletableFuture.supplyAsync() instead.
  • What is CompletionStage? The interface that defines the chaining and composition methods. CompletableFuture implements it.

Visual Overview

DIAGRAM
  Future (Java 5)               CompletableFuture (Java 8)
  ┌──────────────────┐          ┌──────────────────────────┐
  │ get() (blocks)   │          │ thenApply (chain)         │
  │ isDone() (poll)  │          │ thenCompose (flatMap)     │
  │ cancel()         │          │ thenCombine (merge)       │
  │                  │          │ allOf / anyOf             │
  │ No callbacks     │          │ exceptionally (recover)   │
  │ No chaining      │          │ handle (transform)        │
  │ No composition   │          │ whenComplete (observe)    │
  │ No manual        │          │ complete() (manual)       │
  │ completion       │          │ orTimeout (Java 9)        │
  └──────────────────┘          └──────────────────────────┘

Key Takeaway

Future (Java 5) is a simple handle to a pending result. It provides blocking get(), polling isDone(), and cancellation. Nothing else. No callbacks, no chaining, no composition, no error handling pipelines.

CompletableFuture (Java 8) is a complete async programming model. It adds non-blocking callbacks that fire automatically when the result is ready. It adds chaining with thenApply and thenCompose. It adds composition with thenCombine, allOf, and anyOf. It adds error handling with exceptionally, handle, and whenComplete. And it adds manual completion for testing and integration.

The biggest difference is blocking vs non-blocking. Future forces the caller to block or poll. CompletableFuture lets you register callbacks and free the calling thread. At National Bank of India, migrating from Future polling loops to CF callback chains reduced code by 60% and lowered latency by 40% through parallel composition.

Remember one sentence: Future blocks and polls, CompletableFuture chains and composes.

---

Share
Use ← → arrow keys to navigate