Appearance
Lesson 03 · CompletableFuture
Objectives
After this lesson you will be able to:
- Start async work with
supplyAsync/runAsync. - Compose and combine futures with
thenApply,thenCompose,thenCombine. - Handle exceptions with
exceptionally/handle.
Starting async work
CompletableFuture is a Future you can chain — a non-blocking pipeline of stages.
java
CompletableFuture<Integer> cf = CompletableFuture.supplyAsync(() -> 6 * 7); // returns a value
CompletableFuture<Void> run = CompletableFuture.runAsync(() -> doWork()); // no value
cf.join(); // like get() but throws unchecked CompletionExceptionComposing stages
| Method | Use | Function shape |
|---|---|---|
thenApply | transform the result | T → U |
thenAccept | consume the result | T → void |
thenCompose | chain another future (flatMap) | T → CompletableFuture<U> |
thenCombine | merge two independent futures | (T,U) → V |
java
CompletableFuture<String> r = CompletableFuture
.supplyAsync(() -> 21)
.thenApply(n -> n * 2) // 42
.thenApply(n -> "answer=" + n); // "answer=42"
CompletableFuture<Integer> combined =
cfA.thenCombine(cfB, (a, b) -> a + b); // both run, then addExam trap
Use thenApply when the function returns a plain value and thenCompose when it returns another CompletableFuture — thenApply with a future-returning function gives you a nested CompletableFuture<CompletableFuture<U>>. (This is the futures' map vs flatMap.)
Exception handling
java
cf.exceptionally(ex -> fallbackValue); // recover, same type
cf.handle((value, ex) -> ex == null ? value : 0); // see both outcomes
cf.whenComplete((value, ex) -> log(value, ex)); // side-effect, doesn't change resultGotcha
An exception in a stage short-circuits the dependent chain: downstream thenApply/thenAccept stages are skipped until an exceptionally/handle recovers. join() rethrows it as an unchecked CompletionException (wrapping the cause).
Beyond the exam
The *Async variants (thenApplyAsync) run the stage on a different thread/executor; without Async the stage may run on the thread that completed the previous stage. The exam focuses on the non-async composition methods and exception handling.
Key Takeaways
- Start with
supplyAsync(value) /runAsync(no value);join()waits and throwsCompletionException. thenApplytransforms a value;thenComposechains another future (map vs flatMap);thenCombinemerges two.exceptionallyrecovers,handlesees value or exception,whenCompleteobserves without changing the result.- An exception short-circuits dependent stages until a recovery stage handles it.
Lesson Quiz
Your function returns a CompletableFuture<U>. Which method avoids nesting?
What does thenCombine do?
A stage throws. What happens to downstream thenApply stages?
How does join() report a failure?
Next: Virtual Threads. Run the matching code in labs/src/main/java/com/jse21/m07_concurrency/.