Working with CompletableFuture
1. Creating CompletableFuture
| Factory | Use |
|---|---|
completedFuture(v) | Already complete |
failedFuture(t) | Already failed (Java 9+) |
supplyAsync(supplier) | Async with result |
runAsync(runnable) | Async no result |
| Custom executor | 2nd arg of supplyAsync/runAsync |
2. Running Tasks Asynchronously
Example: Async
CompletableFuture<String> cf = CompletableFuture
.supplyAsync(() -> fetchData(), executor);
| Default Executor | Detail |
|---|---|
| Common ForkJoinPool | Shared with parallel streams |
| Caution | Long-running tasks can starve pool |
3. Chaining with thenApply()
| Method | Maps |
|---|---|
thenApply(fn) | T → U (sync) |
thenApplyAsync(fn) | T → U (async) |
4. Chaining with thenAccept()
| Method | Detail |
|---|---|
thenAccept(consumer) | Side effect, returns CF<Void> |
thenRun(runnable) | No input, no output |
5. Chaining with thenCompose()
Example: thenCompose vs thenApply
CompletableFuture<User> user = fetchUser(id);
CompletableFuture<Order> order = user.thenCompose(u -> fetchOrder(u.id())); // flatMap-like
CompletableFuture<CompletableFuture<Order>> nested = user.thenApply(u -> fetchOrder(u.id())); // wrong!
| Method | Use |
|---|---|
thenCompose(fn) | fn returns CompletableFuture (flatMap) |
6. Combining with thenCombine()
| Method | Use |
|---|---|
thenCombine(other, biFn) | Wait for BOTH, combine results |
thenAcceptBoth | Consume both |
runAfterBoth | No values |
7. Handling Multiple Futures (allOf, anyOf)
Example: allOf
List<CompletableFuture<String>> futures = urls.stream()
.map(this::fetchAsync).toList();
CompletableFuture<Void> all = CompletableFuture
.allOf(futures.toArray(CompletableFuture[]::new));
List<String> results = all.thenApply(v ->
futures.stream().map(CompletableFuture::join).toList()
).join();
| Method | Returns |
|---|---|
allOf(...) | CF<Void> — completes when all done |
anyOf(...) | CF<Object> — first to complete |
8. Handling Exceptions
| Method | Detail |
|---|---|
exceptionally(fn) | Recover from exception |
handle((v, e) -> ...) | Process success and failure |
whenComplete((v, e) -> ...) | Side effect, doesn't transform |
exceptionallyCompose(fn) JAVA 12+ | Recover with another CF |
9. Using Custom Executors
| Aspect | Detail |
|---|---|
| Per-call | Pass to ...Async variants |
| Why | Isolate workloads, set bounded threads |
10. Setting Timeouts JAVA 9+
Example: Timeout
CompletableFuture<String> cf = fetchAsync()
.orTimeout(5, TimeUnit.SECONDS)
.completeOnTimeout("default", 4, TimeUnit.SECONDS);
| Method | Detail |
|---|---|
orTimeout(t,u) | Fail with TimeoutException |
completeOnTimeout(v,t,u) | Use default value |
11. Cancelling Futures
| Method | Detail |
|---|---|
cancel(mayInterrupt) | Mark cancelled — does NOT interrupt actual computation |
complete(v) / completeExceptionally(t) | Manual completion |
obtrudeValue/obtrudeException | Force overwrite (use carefully) |
12. Getting Results
| Method | Detail |
|---|---|
get() | Throws checked InterruptedException/ExecutionException |
get(t, u) | Timed get |
join() | Unchecked variant (CompletionException) |
getNow(default) | Non-blocking, default if not done |
13. Composing Async Pipelines
| Pattern | Detail |
|---|---|
| Sequential | thenCompose chain |
| Parallel | allOf + join |
| Race | anyOf or applyToEither |
| Branch + merge | thenCombine after parallel |