Explanation
What just happened
thenCompose ran two calls one after another, because the second call needed the first one’s result (the user). Total wait: both calls added together.
thenCombine started two calls at once, because the weather doesn’t depend on the user. When both were done, render got both results. Total wait: the slower of the two.
Try the what-if switch
allOf fan-out: three independent price calls start together, and allOf(a, b, c).join() waits for the last one. Total time is the slowest call, not the sum. After that, each join() returns at once.
How to choose
Ask: does call B need A’s result?
- Yes:
a.thenCompose(x -> callB(x)), one after another. - No, two calls:
a.thenCombine(b, (x, y) -> ...), side by side. - No, many calls: start them all, then
allOf(...).
Why not thenApply?
fetchUser(42).thenApply(user -> fetchLastOrder(user))
// CompletableFuture<CompletableFuture<Order>>: a future inside a future
When your function already returns a future, thenCompose flattens it, like flatMap on streams.
Watch out
- Chaining independent calls with
thenComposeworks, but you pay for every call in turn. allOfreturnsCompletableFuture<Void>: it tells you when, not what. Read results from the original futures.- If one call fails,
allOffails too, but only after all of them finish. Want the first answer instead? UseanyOf.
Common questions
What is the difference between thenCompose and thenCombine?
thenCompose chains a call that needs the previous result, so the calls run one after another. thenCombine merges two independent futures that run side by side, so you wait only for the slower one.
What is the difference between thenApply and thenCompose?
Use thenCompose when your function returns a CompletableFuture. With thenApply you'd get a future inside a future, CompletableFuture<CompletableFuture<T>>. It's like map vs flatMap.
How do I wait for all CompletableFutures to finish?
Start them all, then call CompletableFuture.allOf(a, b, c).join(). It takes as long as the slowest call. allOf returns CompletableFuture<Void>, so read each result with join() on the original futures.
What happens to allOf when one future fails?
The combined future fails too, but only after all the futures have finished.
Verified: this timeline matches a real program, checked in CI on JDK 21 and 25. How we verify. Unclear or wrong? Tell us