Unravel › Java concepts › Concurrency
CompletableFuture
CompletableFuture represents a result that will arrive later.
Learn these first
At a glance
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.
- 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.
- 2allOf(a, b, c) completes when all do, useful for fan-out callsjoin() or get() finally waits for the value.
- 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).
- Without an executor argument, async steps use the common fork-join pool, which is sized for CPU work; blocking calls there starve everything else
- 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
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; });- line 1 uses CompletableFuture, the core of CompletableFuture
- line 2 uses CompletableFuture, the core of CompletableFuture
- line 3 uses thenCombine, the core of CompletableFuture
- line 4 uses thenAccept, the core of CompletableFuture
- 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
thenApplywherethenComposeis 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:
- Types:
CompletableFutureCompletionStage - Method calls:
supplyAsync()runAsync()thenApply()thenCompose()thenCombine()thenAccept()allOf()anyOf()exceptionally()handle()whenComplete()
Part of a learning path
- Concurrency fundamentals · previous: Executors and thread pools · next: Concurrent collections
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.