CompletableFuture error handling
Recover, observe or transform a failure inside an async chain, and get timeouts under control.
Open this lesson in the learning hubKey points
- A failure skips every
thenApplyandthenComposebelow it and travels down the chain untouched. exceptionallyreplaces the failure with a fallback value, and the chain carries on as if nothing happened.handlereceives both the value and the exception, so it can fold either outcome into one result.whenCompleteonly observes. The failure keeps travelling, which is exactly what you want for logging.- Crossing a stage boundary wraps the cause in
CompletionException, so callgetCause()before reading the message. orTimeoutfails the future withTimeoutException, andcompleteOnTimeoutsupplies a default instead.
Example
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args) {
// a failure skips every mapping stage until something handles it
String recovered = CompletableFuture.<String>supplyAsync(() -> { throw new IllegalStateException("db down"); })
.thenApply(s -> s + " (never runs)")
.exceptionally(ex -> "fallback: " + ex.getCause().getMessage())
.join();
System.out.println("exceptionally : " + recovered);
// handle sees both outcomes and may change the type
String handled = CompletableFuture.supplyAsync(() -> 21 * 2)
.handle((value, ex) -> ex == null ? "ok " + value : "failed " + ex.getMessage())
.join();
System.out.println("handle : " + handled);
// whenComplete observes only: the failure still travels on
CompletableFuture<String> observed = CompletableFuture
.<String>failedFuture(new IllegalArgumentException("bad id"))
.whenComplete((v, ex) -> System.out.println("whenComplete : saw " + ex.getClass().getSimpleName()));
System.out.println("still failed : " + observed.isCompletedExceptionally());
// a timeout is just another failure you can recover from
String timed = CompletableFuture.supplyAsync(() -> { sleep(500); return "slow"; })
.orTimeout(100, TimeUnit.MILLISECONDS)
.exceptionally(ex -> "recovered from " + ex.getClass().getSimpleName())
.join();
System.out.println("orTimeout : " + timed);
}
static void sleep(long ms) {
try { Thread.sleep(ms); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
}
Handle failure inside the chain, not after join() has already thrown.
This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Multithreading course, and every lesson in it is listed on the Multithreading contents page.