CompletableFuture.whenComplete observes an outcome, while handle can produce a replacement value after success or failure.
Java CompletableFuture handle versus whenComplete: recovery is explicit
Logging does not repair the stage
The failed lookup is observed by whenComplete, which prints the exception type. Joining that observed stage still fails. handle returns a fallback review state, so its dependent stage succeeds. These synchronous callbacks run as the source completes in this fixture; no executor is involved.
A fallback can conceal a real incident. Keep the original failure in telemetry and define when fallback is acceptable. allOf makes a separate batch-wide decision.
Avoid blocking inside callbacks
Callbacks can execute on a completion thread. A remote retry or long blocking operation inside handle can delay other work. Use a deliberately owned executor for expensive asynchronous stages and define its shutdown path.
Working program
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
public class ReceiptRecoveryStage {
public static void main(String[] args) {
CompletableFuture<String> lookup = new CompletableFuture<>();
CompletableFuture<String> observed = lookup.whenComplete((value, failure) ->
System.out.println("observed=" + failure.getClass().getSimpleName()));
CompletableFuture<String> recovered = lookup.handle((value, failure) ->
failure == null ? value : "manual review");
lookup.completeExceptionally(new IllegalArgumentException("unknown receipt"));
try { observed.join(); }
catch (CompletionException failure) {
System.out.println("still=" + failure.getCause().getClass().getSimpleName());
}
System.out.println("fallback=" + recovered.join());
}
}Output
observed=IllegalArgumentException
still=IllegalArgumentException
fallback=manual reviewCosts and boundaries
This fixture checks stage outcomes but does not measure callback scheduling, retry traffic, or remote-service behavior. A fallback is an application policy, not an automatic fix for the upstream failure.
Common Mistakes
- Do not expect whenComplete to consume the original failure.
- Do not return a generic success fallback for every exception without a policy.
- Do not block an uncontrolled completion thread with remote work.
Read next
Java CompletableFuture: composition, failures and executor ownership, Java CompletableFuture.allOf: completion barrier, not typed results, Java ExecutorService: bounded admission and shutdown.
