What does anyOf() method do and when is it useful
The static utility method CompletableFuture.anyOf(CompletableFuture>... cfs) accepts a variable number of asynchronous tasks and returns a new CompletableFuture
🟢 Junior Level
The static utility method CompletableFuture.anyOf(CompletableFuture<?>... cfs) accepts a variable number of asynchronous tasks and returns a new CompletableFuture<Object> that completes as soon as any first of the supplied futures finishes execution (either successfully or exceptionally).
The response latency of anyOf equals the latency of the fastest task: $\min(t_1, t_2, \dots, t_N)$.
Basic Example: Fastest Replica Wins
CompletableFuture<String> primaryDc = CompletableFuture.supplyAsync(() -> queryDc("dc-east"));
CompletableFuture<String> replicaDc = CompletableFuture.supplyAsync(() -> queryDc("dc-west"));
// Await the fastest response between the two data centers:
CompletableFuture<Object> fastest = CompletableFuture.anyOf(primaryDc, replicaDc);
fastest.thenAccept(result -> {
// Note: the result is of type Object; casting is required
String response = (String) result;
System.out.println("Fastest response received: " + response);
});
Primary Use Cases
- Hedged Requests: Dispatching identical read requests to multiple independent replicas to shave off p99 tail latency.
- Racing Redundant Data Sources: Querying an in-memory cache and a primary database concurrently, where the cache usually responds first.
- Async Timeout Emulation (Java 8): Racing the target computation against a delayed scheduled future.
🟡 Middle Level
Key Architectural Characteristics of anyOf
1. Type Erasure to CompletableFuture<Object>
Because anyOf accepts heterogeneous futures (CompletableFuture<User>, CompletableFuture<Order>, CompletableFuture<Double>), it returns CompletableFuture<Object>.
For homogeneous tasks returning the same type, developers typically construct a typed helper:
@SuppressWarnings("unchecked")
public static <T> CompletableFuture<T> anyOfTyped(List<CompletableFuture<T>> futures) {
return (CompletableFuture<T>) CompletableFuture.anyOf(
futures.toArray(new CompletableFuture[0])
);
}
2. Losing Tasks Are NOT Cancelled Automatically
When the winning future completes, all remaining futures continue executing in their worker pools, consuming CPU, memory, socket connections, and database locks. If their outcomes are obsolete, they must be cancelled explicitly:
List<CompletableFuture<Response>> futures = sendToAllReplicas();
CompletableFuture<Object> any = CompletableFuture.anyOf(futures.toArray(new CompletableFuture[0]));
any.thenAccept(winner -> {
// Cancel stragglers to prevent resource leaks:
for (CompletableFuture<Response> f : futures) {
if (!f.isDone()) {
f.cancel(true);
}
}
});
Comparison: anyOf vs applyToEither
| Criterion | applyToEither / acceptEither |
CompletableFuture.anyOf |
|---|---|---|
| Arity | Exactly 2 futures | Arbitrary ($N$) futures |
| Type Safety | Statically typed (T $\rightarrow$ V) |
Untyped (Object) |
| Input Types | Must share the same type $T$ | Can be completely heterogeneous |
🔴 Senior Level
Internal Architecture: The OrRelay Binary Tree
Like allOf, anyOf builds a balanced binary tree over the input array, but replaces BiRelay with OrRelay nodes:
// Conceptual JDK implementation:
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs) {
int n = cfs.length;
if (n == 0) return new CompletableFuture<Object>(); // WARNING: Hangs forever!
if (n == 1) return (CompletableFuture<Object>) cfs[0].copy();
return orTree(cfs, 0, n - 1);
}
- Each
OrRelaynode registers completion listeners on both its left and right subtrees. - Whichever thread completes the first child leaf node uses an atomic CAS operation to push its value into the root
OrRelay. - Subsequent completion signals from other tasks are discarded by CAS failure.
The Fatal Trap: anyOf Short-Circuits on the FIRST ERROR
The most dangerous misconception in asynchronous architecture is assuming anyOf returns the “first successful result”.
This is NOT true. anyOf returns the first terminating event, whether it is a success or an exception:
- If 3 replicas are queried and Replica A fails with
Connection refusedin 1 ms,anyOfimmediately completes exceptionally! - It will not wait for Replicas B and C, even if they would have succeeded 5 ms later.
The First-Successful Pattern (True Hedging)
To implement true fault-tolerant hedged requests, custom coordination logic is required:
public static <T> CompletableFuture<T> firstSuccessful(List<CompletableFuture<T>> futures) {
CompletableFuture<T> promise = new CompletableFuture<>();
AtomicInteger remaining = new AtomicInteger(futures.size());
List<Throwable> errors = new CopyOnWriteArrayList<>();
for (CompletableFuture<T> f : futures) {
f.whenComplete((val, ex) -> {
if (ex == null) {
// First success claims the promise and cancels all competitors:
if (promise.complete(val)) {
futures.forEach(other -> other.cancel(true));
}
} else {
errors.add(ex);
// If ALL futures fail, complete exceptionally with all aggregated errors:
if (remaining.decrementAndGet() == 0) {
CompletionException aggregate = new CompletionException("All replicas failed", errors.get(0));
errors.forEach(aggregate::addSuppressed);
promise.completeExceptionally(aggregate);
}
}
});
}
return promise;
}
4 Tricky Questions
1. What is returned when CompletableFuture.anyOf() is called with zero arguments (anyOf())?
Answer:
It returns an incomplete CompletableFuture<Object> that will hang forever!
In the JDK implementation:
if (cfs.length == 0) return new CompletableFuture<Object>();
This returns an uncompleted instance with no registered completion source. Calling join() on it blocks the caller thread indefinitely.
By contrast, CompletableFuture.allOf() with zero arguments immediately returns an already completed CompletableFuture<Void> holding null.
2. If the first future to finish in anyOf throws an exception, will anyOf return the result of the second future if it completes successfully 1 millisecond later?
Answer:
No, it will not.
anyOf transitions to a terminal state on the very first completion event. If the earliest finishing task fails, anyOf completes exceptionally with CompletionException immediately. The subsequent successful outcome of the second task is ignored.
3. If tasks passed to anyOf return different types (e.g., String and Integer), how should the result be safely handled without risking ClassCastException?
Answer:
Use pattern matching for instanceof or switch expressions (Java 16+ / 21+):
CompletableFuture.anyOf(fetchNameAsync(), fetchCountAsync())
.thenAccept(obj -> {
switch (obj) {
case String s -> processName(s);
case Integer i -> processCount(i);
case null -> log.warn("Result was null");
default -> throw new IllegalStateException("Unexpected type: " + obj.getClass());
}
});
4. Which thread executes a callback registered via .thenAccept(...) on anyOf?
Answer:
The callback executes on the worker thread that finished the winning task (the one that triggered the CAS transition of anyOf). If one of the futures was already finished prior to attaching .thenAccept(...), the callback executes synchronously on the caller thread. When isolating compute or I/O workloads, always use thenAcceptAsync(..., dedicatedExecutor).
🎯 Interview Cheat Sheet
Summary in 60 Seconds
CompletableFuture.anyOf()races $N$ futures; completes as soon as any first future finishes.- Returns
CompletableFuture<Object>. Latency = $\min(t_1, t_2, \dots, t_N)$. - Reacts to the first terminal state: if the fastest task throws an exception,
anyOffails immediately. - Does not cancel remaining tasks: stragglers continue executing in the background unless explicitly cancelled.
- Calling
anyOf()with an empty array produces a future that never completes. - True resilient hedging (“first successful”) requires custom coordination logic.
Common Interview Mistakes
- Assuming
anyOfwaits for the first successful response (it fails fast if the earliest task fails). - Assuming
anyOfcancels the other running tasks. - Forgetting that
anyOfreturnsCompletableFuture<Object>rather than a parameterized type.