← Home
🏗Crack System Design with DSA AnimatorSystem DesignNEW
🏗 System Design›Concurrency & Threading

🔗CompletableFuture & Async Composition

Compose asynchronous workflows cleanly, with explicit timeout and failure behavior.

📌 Key IdeaCompletableFuture is Java's promise-style API for async orchestration. Use thenCompose for dependent calls, thenCombine/allOf for parallel fan-out, and always define executor, timeout, and fallback strategy.
🧩

Problem it solves

CompletableFuture solves the problem of slow, sequential waiting between independent I/O calls. Instead of blocking one call after another, it lets you run them concurrently and compose results in a readable pipeline.

✨

Why It Matters

  • Latency reduction — Parallel downstream calls can significantly reduce end-to-end response time.
  • Readable async flow — Avoids callback pyramids with explicit composition primitives.
  • Controlled failure behavior — Timeouts and fallbacks can be attached directly to stages.
✅

When to Use

  • Parallel downstream calls in service aggregation APIs.
  • Non-blocking orchestration where end-to-end latency matters.
  • Structured async flows with fallback/timeout behavior.
🚫

When NOT to Use

  • Simple sequential code where async adds complexity.
  • Blocking .get() everywhere (defeats async benefit).
💻

Code Variations

0️⃣ Problem it solves (sequential wait vs async composition)java
// ❌ Sequential: total latency ~= A + B
User user = userClient.fetch(userId);      // 120ms
List<Order> orders = orderClient.fetch(userId); // 150ms
Dashboard d1 = new Dashboard(user, orders); // ~270ms+

// ✅ CompletableFuture: run in parallel, then combine
CompletableFuture<User> userF = CompletableFuture.supplyAsync(
    () -> userClient.fetch(userId)
);
CompletableFuture<List<Order>> ordersF = CompletableFuture.supplyAsync(
    () -> orderClient.fetch(userId)
);

Dashboard d2 = userF.thenCombine(ordersF, Dashboard::new).join(); // ~max(A,B)
Simple rule: if calls are independent and I/O-bound, compose asynchronously instead of waiting sequentially.
1️⃣ Dependent async chain (thenCompose)java
CompletableFuture<User> f = fetchUser(id)
    .thenCompose(user -> fetchProfile(user.profileId()));
Use thenCompose to flatten future-of-future.
2️⃣ Fan-out + combine + timeoutjava
CompletableFuture<A> a = callA();
CompletableFuture<B> b = callB();
return a.thenCombine(b, this::merge)
        .orTimeout(300, MILLISECONDS)
        .exceptionally(ex -> fallback());
Always define timeout/error policy for remote calls.
3️⃣ Aggregation endpoint examplejava
CompletableFuture<User> userF = userClient.fetch(userId);
CompletableFuture<List<Order>> ordersF = orderClient.fetchRecent(userId);
CompletableFuture<CreditScore> scoreF = riskClient.fetchScore(userId);

return CompletableFuture.allOf(userF, ordersF, scoreF)
    .thenApply(v -> new Dashboard(userF.join(), ordersF.join(), scoreF.join()))
    .orTimeout(500, MILLISECONDS);
Use join() only after allOf completion inside the async pipeline.
4️⃣ Custom executor (avoid commonPool surprises)java
ExecutorService ioPool = Executors.newFixedThreadPool(32);

CompletableFuture<User> f = CompletableFuture.supplyAsync(
    () -> userRepo.load(id), ioPool
);
Explicit executors prevent contention with unrelated workloads on ForkJoinPool.commonPool().
📊

Core CompletableFuture operations

AspectMethodUse whenReturns
thenApply(fn)Transform value synchronouslyCompletableFuture<U>
thenCompose(fn)Next step itself is asyncFlattened CompletableFuture<U>
thenCombine(other, fn)Merge two independent futuresCompletableFuture<U>
allOf(f1,f2,...)Wait for many futuresCompletableFuture<Void>
exceptionally(fn)Map failure to fallback valueCompletableFuture<T>
🔀

Common Confusions Cleared Up

thenApply vs thenCompose

thenApply maps value; thenCompose chains another async future and flattens.

join vs get

join() throws unchecked CompletionException; get() throws checked exceptions and often encourages blocking patterns.

🎤

Interview Q&A

🧾

Quick Cheat Sheet

Dependent chainthenCompose
Parallel combinethenCombine, allOf
Safetytimeouts + exception handling + bounded executors
PerformanceAvoid blocking .get()/.join() on request threads
🔗

Related Topics