Skip to content

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 CompletionException

Composing stages

MethodUseFunction shape
thenApplytransform the resultT → U
thenAcceptconsume the resultT → void
thenComposechain another future (flatMap)T → CompletableFuture<U>
thenCombinemerge 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 add

Exam trap

Use thenApply when the function returns a plain value and thenCompose when it returns another CompletableFuturethenApply 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 result

Gotcha

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 throws CompletionException.
  • thenApply transforms a value; thenCompose chains another future (map vs flatMap); thenCombine merges two.
  • exceptionally recovers, handle sees value or exception, whenComplete observes without changing the result.
  • An exception short-circuits dependent stages until a recovery stage handles it.

Lesson Quiz

Lesson Quiz · CompletableFuture0 / 4
  1. Your function returns a CompletableFuture<U>. Which method avoids nesting?

    • AthenApply
    • BthenCompose
    • CthenAccept
    • DthenRun
  2. What does thenCombine do?

    • ARuns a fallback
    • BMerges the results of two independent futures
    • CChains a dependent future
    • DCancels both
  3. A stage throws. What happens to downstream thenApply stages?

    • AThey run with null
    • BThey are skipped until exceptionally/handle recovers
    • CThey retry
    • DNothing changes
  4. How does join() report a failure?

    • AReturns null
    • BThrows checked ExecutionException
    • CThrows unchecked CompletionException
    • DHangs

Next: Virtual Threads. Run the matching code in labs/src/main/java/com/jse21/m07_concurrency/.