Compose two dependent async stages with CompletableFuture
Given two async helpers, fetchUser(id) returning CompletableFuture<User> and fetchOrders(user) returning CompletableFuture<List<Order>> (each Order has an int amount()), compose them so the second call starts only after the first completes, then return the user's total spend.
Constraints:
- the two calls run as a non-blocking chain — no
get()/join()anywhere - the returned future must be a flat
CompletableFuture<Integer>, not a nested future
CompletableFuture<Integer> totalSpent(String userId) {
// your code here
}
Write the implementation.
CompletableFuture is a Future you can complete yourself and chain into stages. thenApply maps a completed value with a plain function; thenCompose chains a stage that itself returns a CompletableFuture and flattens the result, so dependent async calls run in order with no blocking get().
- ✗Calling
get()between stages, turning an async chain back into blocking code - ✗Using
thenApplywhere the mapper returns a future, producing a nestedCompletableFuture - ✗Believing
CompletableFuturecannot be completed manually or composed
- →When would you reach for
thenComposeinstead ofthenApply? - →How does
thenCombinediffer fromthenComposewhen joining two futures?
Solution
CompletableFuture<Integer> totalSpent(String userId) {
return fetchUser(userId) // CompletableFuture<User>
.thenCompose(user -> fetchOrders(user)) // flattens CF<List<Order>>
.thenApply(orders -> orders.stream() // maps the completed value
.mapToInt(Order::amount)
.sum());
}
Idea. fetchOrders depends on the result of fetchUser and itself returns a CompletableFuture. Using thenApply here would give a nested CompletableFuture<CompletableFuture<List<Order>>>. thenCompose chains the dependent stage and flattens it into one flat future — it is the flatMap of futures. The final thenApply is a pure transform of the completed list, so no blocking get() is needed anywhere: the work runs in callbacks once the previous stage completes.