What does allOf() method do and when to use it
Structured Java interview answer with junior, middle, and senior-level explanation.
π’ Junior Level
allOf() β waits for ALL passed CompletableFutures to complete and returns a new CompletableFuture.
CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<String> cf3 = CompletableFuture.supplyAsync(() -> "!");
// Waits for all three to complete
CompletableFuture<Void> all = CompletableFuture.allOf(cf1, cf2, cf3);
all.thenRun(() -> {
// All completed β can now get results
System.out.println(cf1.join() + " " + cf2.join() + " " + cf3.join());
// Hello World !
});
Important: allOf() returns CompletableFuture<Void> β it does not contain the results, you need to call join() on each original CF.
π‘ Middle Level
How it works
CompletableFuture<User> user = getUserAsync(userId);
CompletableFuture<Order> order = getOrderAsync(userId);
CompletableFuture<Preferences> prefs = getPrefsAsync(userId);
// Wait for all
CompletableFuture<Void> all = CompletableFuture.allOf(user, order, prefs);
// After all complete
all.thenRun(() -> {
User u = user.join(); // does not block β already ready
Order o = order.join();
Preferences p = prefs.join();
process(new UserProfile(u, o, p));
});
Error handling
// If at least one fails β allOf will also fail
CompletableFuture.allOf(cf1, cf2, cf3)
.exceptionally(ex -> {
// ex β CompletionException with cause
log.error("One of the CFs failed", ex);
return null;
});
Typical mistakes
- Trying to get results from allOf:
```java
// β allOf returns Void!
CompletableFuture
all = CompletableFuture.allOf(cf1, cf2); // all.join(); // null!
// β Need join on each CF CompletableFuture.allOf(cf1, cf2).thenRun(() -> { String r1 = cf1.join(); String r2 = cf2.join(); });
---
## π΄ Senior Level
### Internal Implementation
```java
// Internally allOf creates a dependency tree and completes
// when all passed CFs are done. Real internal JDK class names differ β
// this is a conceptual description.
Architectural Trade-offs
| allOf() | thenCombine() |
|---|---|
| Many CFs (2+) | Two CFs |
| Returns Void | Returns result |
| Need join for results | Results in callback |
Edge Cases
1. Collect results pattern:
public CompletableFuture<List<User>> getUsers(List<Long> ids) {
List<CompletableFuture<User>> futures = ids.stream()
.map(id -> getUserAsync(id))
.toList();
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenApply(v ->
futures.stream()
.map(CompletableFuture::join)
.toList()
);
}
2. Partial results with timeout:
CompletableFuture<Void> all = CompletableFuture.allOf(cf1, cf2, cf3);
all.orTimeout(5, TimeUnit.SECONDS)
.exceptionally(ex -> {
// Timeout or error
// But some CFs may have completed
if (cf1.isDone()) System.out.println("cf1 done");
if (cf2.isDone()) System.out.println("cf2 done");
return null;
});
Performance
allOf():
- Creation: ~10 ns
- Overhead per CF: ~5 ns
- For N CFs β tree of depth log(N)
join() on a ready CF: ~1 ns (does not block)
Production Experience
Batch API calls:
@Service
public class DashboardService {
public CompletableFuture<Dashboard> getDashboard(Long userId) {
CompletableFuture<User> user = userService.findByIdAsync(userId);
CompletableFuture<List<Order>> orders = orderService.findByUserIdAsync(userId);
CompletableFuture<List<Notification>> notifications =
notificationService.findByUserIdAsync(userId);
CompletableFuture<Stats> stats = statsService.getStatsAsync(userId);
return CompletableFuture.allOf(user, orders, notifications, stats)
.thenApply(v -> new Dashboard(
user.join(),
orders.join(),
notifications.join(),
stats.join()
));
}
}
Best Practices
// β
allOf for parallel independent calls
CompletableFuture.allOf(cf1, cf2, cf3)
.thenRun(() -> combine(cf1.join(), cf2.join(), cf3.join()));
// β
Error handling
CompletableFuture.allOf(cf1, cf2)
.exceptionally(ex -> handleErrors(ex, cf1, cf2));
// β allOf when sequential is needed
// β Ignoring that allOf returns Void
When NOT to use allOf
- Need results as they become ready β process each CF separately
- One failure should not cancel the rest β allOf throws on the first error
// β allOf β one error brings everything down
CompletableFuture.allOf(cf1, cf2, cf3)
.exceptionally(ex -> null); // all results lost
// β
Handle each CF separately
cf1.handle((r, ex) -> ex != null ? fallback1 : r);
cf2.handle((r, ex) -> ex != null ? fallback2 : r);
cf3.handle((r, ex) -> ex != null ? fallback3 : r);
π― Interview Cheat Sheet
Must know:
- allOf() accepts N CompletableFutures, returns CompletableFuture
- Completes when ALL CFs are done, or on the first error
- To get results, call join() on each original CF
- Latency = max(all CFs) β all execute in parallel
- Pattern allAsList: allOf + stream.map(CompletableFuture::join).toList()
Frequent follow-up questions:
- Does allOf return results? β No, Void. join() on each CF separately
- What if one CF fails? β allOf completes with a CompletionException
- Does allOf block? β No, it returns a CF. allOf().join() blocks
- How to get partial results on error? β Check isDone() on each CF
Red flags (DO NOT say):
- βallOf returns an array of resultsβ β it returns Void
- βallOf continues when one CF failsβ β it completes with an error
- βallOf().join() is non-blockingβ β join() blocks until completion
Related topics:
- [[10. What does anyOf() method do and when is it useful]]
- [[8. How to combine results of multiple CompletableFutures]]
- [[16. How to properly execute multiple parallel requests to microservices]]
- [[6. How to handle exceptions in CompletableFuture chain]]