Lesson 28 • Advanced
CompletableFuture & Async Programming
Chain async operations and handle results with CompletableFuture.
Before You Start
You need Multithreading (Lesson 26), Lambda Expressions (Lesson 22), and Streams API (Lesson 21). CompletableFuture is Java's Promise — it combines async execution with functional-style chaining.
What You'll Learn
- ✅ supplyAsync() and runAsync()
- ✅ thenApply(), thenAccept(), thenRun()
- ✅ thenCompose() vs thenCombine()
- ✅ Exception handling: exceptionally(), handle()
- ✅ allOf() and anyOf() for parallel tasks
1️⃣ From Callbacks to Pipelines
💡 Analogy: Food Delivery App
supplyAsync() is like placing an order — it starts in the background. thenApply() is like "when food arrives, add tip to receipt." thenCompose() is like "when food arrives, order dessert from same place" (sequential async). thenCombine() is ordering food and drinks simultaneously, combining when both arrive. exceptionally() is your backup — "if order fails, get pizza."
Try It: Async Chaining Basics
// CompletableFuture — Async Chaining
console.log("=== CompletableFuture Basics ===\n");
// 1. supplyAsync + thenApply (simulated with Promises)
console.log("1. supplyAsync + thenApply:");
let fetchUser = Promise.resolve({ id: 1, name: "Alice", age: 28 });
fetchUser
.then(user => { console.log(" Fetched: " + user.name); return user.name.toUpperCase(); })
.then(name => console.log(" Transformed: " + name));
// 2. Chaining multiple steps
console.log("\n2. CHAINING OPERATIONS:");
Promis
...2️⃣ thenCompose vs thenCombine
thenCompose = sequential async (flatMap). Use when second operation depends on first: fetch user → then fetch user's orders. thenCombine = parallel async. Use when two operations are independent: fetch price AND fetch discount → calculate total.
Try It: Compose, Combine & Error Handling
// thenCompose, thenCombine & Error Handling
console.log("=== Compose & Combine ===\n");
// 1. thenCompose (sequential — flatMap)
console.log("1. thenCompose (SEQUENTIAL ASYNC):");
function getUserById(id) { return Promise.resolve({ id, name: "User-" + id }); }
function getOrdersForUser(user) { return Promise.resolve(["Order-A", "Order-B"]); }
getUserById(42)
.then(user => { console.log(" Step 1: Found " + user.name); return getOrdersForUser(user); })
.then(orders => console.log(" St
...Try It: Real-World Async Patterns
// Real-World Async Patterns
console.log("=== Async Patterns ===\n");
// 1. Microservice aggregation
console.log("1. MICROSERVICE AGGREGATION:");
function fetchUserProfile(id) { return Promise.resolve({ name: "Alice", role: "admin" }); }
function fetchUserOrders(id) { return Promise.resolve([{ id: 101, total: 59.99 }, { id: 102, total: 124.50 }]); }
function fetchRecommendations(id) { return Promise.resolve(["Product A", "Product B", "Product C"]); }
let userId = 42;
Promise.all([
fetchUse
...Common Mistakes
future.get() blocks forever. Always use get(5, TimeUnit.SECONDS).Pro Tips
💡 handle() vs exceptionally(): handle() receives both result and exception — use for logging regardless of outcome.
💡 orTimeout() (Java 9+): future.orTimeout(5, SECONDS) — no more hanging futures.
💡 allOf() returns Void — collect results with futures.stream().map(CF::join).toList().
📋 Quick Reference
| Method | Type | Purpose |
|---|---|---|
| supplyAsync() | Create | Start async with return value |
| thenApply() | Transform | Map result (like .map()) |
| thenCompose() | Chain | Sequential async (flatMap) |
| thenCombine() | Combine | Parallel async merge |
| exceptionally() | Error | Handle errors with fallback |
🎉 Lesson Complete!
You've mastered async programming with CompletableFuture!
Next: Memory Management & JVM Garbage Collection.
Sign up for free to track which lessons you've completed and get learning reminders.