CompletableFuture.thenCompose starts a stage that depends on a prior result and flattens its CompletionStage into one resulting future.
Java thenCompose: flatten a dependent asynchronous request
Operational contract
A receipt lookup cannot start until a shipment lookup supplies its identifier. The method accepts both operations as functions so its caller owns transport, timeout, and executor policy. thenApply would return a nested future and make completion ownership harder to reason about. If the first stage fails, the lookup function is not called; if the second fails, the composed stage fails. Neither failure automatically cancels other work.
Failure case
The shipment lookup fails before it has an ID. No receipt lookup runs. If the receipt lookup never completes, the composed future also never completes unless the caller applies a deadline or completes it by another policy.
Java code
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
public class ShipmentReceiptChain {
public static CompletableFuture<String> receipt(
CompletableFuture<Long> shipmentLookup,
Function<Long, CompletableFuture<String>> receiptLookup) {
Objects.requireNonNull(shipmentLookup);
Objects.requireNonNull(receiptLookup);
return shipmentLookup.thenCompose(receiptLookup);
}
}Performance and ownership cost
Registering one dependent stage uses O(1) bookkeeping. End-to-end latency is the first operation followed by the second, so these dependent calls cannot run concurrently. The transport and executor determine the actual work and memory cost.
Common Mistakes
- Do not use thenApply when the callback returns another future and a flat result is required.
- Do not assume a failed first stage invokes the second request.
- Do not confuse composition with a deadline or cancellation policy.
