Concurrency
CompletableFuture
CompletableFuture<T> is a promise you can compose: chain transformations, combine independent results, race alternatives, and attach error handling — asynchronous workflows without callback pyramids or blocked threads.- Create:
supplyAsync(supplier, executor); pass your own executor — the default is the common FJ pool - Chain:
thenApply(map),thenCompose(flatMap),thenCombine(zip two) - Errors flow down the chain to
exceptionally/handle— an unobserved failure vanishes silently allOf/anyOffor fan-in;orTimeout/completeOnTimeout(Java 9) for deadlinesthenApplyvsthenApplyAsync: same-thread continuation vs re-dispatch to a pool- With virtual threads, plain blocking code often replaces CF chains entirely
CompletableFuture<Quote> best =
CompletableFuture.supplyAsync(() -> fetchUser(id), ioPool)
.thenCompose(user -> CompletableFuture.supplyAsync(
() -> fetchQuotes(user), ioPool)) // async dependency
.thenApply(quotes -> quotes.stream()
.min(comparing(Quote::price)).orElseThrow()) // pure transform
.orTimeout(2, TimeUnit.SECONDS)
.exceptionally(ex -> {
log.warn("quote lookup failed", ex);
return Quote.DEFAULT; // fallback
});
Quote q = best.join(); // only the final consumer blocks (if anyone must)List<CompletableFuture<Price>> calls = vendors.stream()
.map(v -> CompletableFuture.supplyAsync(() -> v.quote(item), ioPool))
.toList();
CompletableFuture<List<Price>> all =
CompletableFuture.allOf(calls.toArray(CompletableFuture[]::new))
.thenApply(ignored -> calls.stream().map(CompletableFuture::join).toList());Threading model: non-Async continuations run on whichever thread completed the previous stage (possibly your thread at composition time if it was already done); *Async variants re-dispatch to the pool. Keep continuations short and non-blocking, or make them Async on a suitable executor. And reassess with Virtual Threads: vthreadExecutor.submit(() -> { straightLineBlockingCode(); }) reads better than most CF chains and scales identically (Core Java and OCNJ both make this point).