BackendBench
Coffee

Unravel › Java concepts › Concurrency

Concurrency

CompletableFuture

CompletableFuture represents a result that will arrive later.

Learn these first

At a glance

Mental model

Ordering food with a pager: you note what to do when it buzzes (collect, add sauce, carry to the table) and go about your day.

What happens, in order
  1. 1supplyAsync(() -> fetch()) starts work on a pool thread and returns a futurethenApply(x -> parse(x)) transforms the result when ready; thenCompose chains another asynchronous step; thenCombine joins two independent futures.
  2. 2allOf(a, b, c) completes when all do, useful for fan-out callsjoin() or get() finally waits for the value.
  3. 3Errors travel along the chain: exceptionally(ex -> fallback) or handle((v, ex) -> ...) converts themWithout a handler an exception is only seen when you call join (as CompletionException).
Remember
Watch out
Blocking calls on the common poolDropping a future with no error handlingCalling join() immediately, which turns async back into syncUsing thenApply where thenCompose is needed (nested futures)
Example (highlighted lines explain it)
CompletableFuture<Order> order = CompletableFuture.supplyAsync(() -> loadOrder(id), pool);CompletableFuture<Customer> customer = CompletableFuture.supplyAsync(() -> loadCustomer(id), pool);order.thenCombine(customer, Invoice::new)     .thenAccept(this::send)     .exceptionally(ex -> { log.error("invoice failed", ex); return null; });
  1. line 1 uses CompletableFuture, the core of CompletableFuture
  2. line 2 uses CompletableFuture, the core of CompletableFuture
  3. line 3 uses thenCombine, the core of CompletableFuture
  4. line 4 uses thenAccept, the core of CompletableFuture
  5. line 5 uses exceptionally, the core of CompletableFuture
Read the full explanation (about 1 min)

CompletableFuture represents a result that will arrive later. You chain steps on it ("when it is done, transform it, then call this") instead of blocking a thread to wait.

A mental model

Ordering food with a pager: you note what to do when it buzzes (collect, add sauce, carry to the table) and go about your day.

How it works

supplyAsync(() -> fetch()) starts work on a pool thread and returns a future. thenApply(x -> parse(x)) transforms the result when ready; thenCompose chains another asynchronous step; thenCombine joins two independent futures.

allOf(a, b, c) completes when all do, useful for fan-out calls. join() or get() finally waits for the value.

Errors travel along the chain: exceptionally(ex -> fallback) or handle((v, ex) -> ...) converts them. Without a handler an exception is only seen when you call join (as CompletionException).

Going deeper

Without an executor argument, async steps use the common fork-join pool, which is sized for CPU work; blocking calls there starve everything else. Pass your own executor for I/O. thenApply may run on the thread that completed the previous step or on the caller, so do not assume which. Forgotten futures hide failures, so always end a chain with a handler or a join.

Under the hood

Each stage is a small object registered as a dependent of the previous future; when a future completes it runs its dependents, on its own thread or the supplied executor.

Common mistakes

  • Blocking calls on the common pool.
  • Dropping a future with no error handling.
  • Calling join() immediately, which turns async back into sync.
  • Using thenApply where thenCompose is needed (nested futures).

How Unravel recognises it

When you paste code, Unravel looks for these signs of completablefuture. Spotting them yourself is a good habit when you read unfamiliar code:

Part of a learning path

Related concepts

Frequently asked questions

What is CompletableFuture?

CompletableFuture represents a result that will arrive later. You chain steps on it ("when it is done, transform it, then call this") instead of blocking a thread to wait.

What are the most common mistakes with CompletableFuture?

Blocking calls on the common pool. Dropping a future with no error handling. Calling join() immediately, which turns async back into sync. Using thenApply where thenCompose is needed (nested futures).

How can I see CompletableFuture in real code?

Paste your own Java into Unravel and it will point out where completablefuture appears, explain how it works at the detail level you choose, and draw the flow of the code around it.