What are the typing and design trade-offs of CompletableFuture.anyOf, and when would you reach for it?
answer
- anyOf = first of N to settle
- Result is Object (heterogeneous inputs, winner type unknown at compile time)
- Settles on first finish, success OR failure
- Losers not cancelled → N-1 wasted, uninterruptible work
- StructuredTaskScope.ShutdownOnSuccess = cancel-the-losers alternative
basics
~20 sanyOf completes as soon as the first of many futures finishes, returning that result. Because the inputs may differ in type, its result type is Object, so you usually cast or restrict inputs to one type.
solid answer
~40 sanyOf(cf1, cf2, ...) returns a CompletableFuture<Object> that settles with the result (or exception) of whichever input settles first. Its result is Object because the varargs accept heterogeneous CompletableFuture<?>, so the API can't name a more specific type — in practice you either feed it homogeneous futures and cast, or wrap it. It's the many-input analogue of applyToEither, useful for racing N redundant sources, first-to-respond hedging, or racing real work against a timeout future. Key caveats: like applyToEither it races on *settlement* not success (a first failure propagates), and the losing futures are *not* cancelled — they run on and consume resources. At scale you weigh that wasted work; structured concurrency (JEP 'StructuredTaskScope.ShutdownOnSuccess') is the cleaner modern tool because it actually cancels the losers. Prefer anyOf for simple races where leftover work is cheap.
go deeper
Knows anyOf returns when the first of several futures completes.
Explains that anyOf returns CompletableFuture<Object> because inputs may differ in type, and that it's the many-input version of applyToEither.
Notes the settle-vs-succeed behavior, that losers aren't cancelled, and handles the Object result via homogeneous inputs + cast or wrapping.
Weighs the wasted-work and resource cost of uncancelled losers at scale, contrasts anyOf with StructuredTaskScope.ShutdownOnSuccess (which cancels losers and races on success), and chooses the right primitive for hedging/timeout designs.
## The 'first of many' problem `applyToEither` races **two** futures. `anyOf` generalises that to **N** futures: complete as soon as the **first** of an arbitrary set settles. ## Signature and the Object problem ``` static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs) ``` It takes varargs of `CompletableFuture<?>` (heterogeneous, unknown element types) and returns `CompletableFuture<Object>`. Why `Object`? The winner could be **any** of the input types, and at compile time the API cannot know which one wins, so the only common supertype it can promise is `Object`. This is the cost of accepting heterogeneous inputs. In practice you handle the `Object` in one of two ways: - **Homogeneous inputs:** if all inputs are `CompletableFuture<String>`, you know the winner is a `String` and cast: `(String) anyOf(...).join()`. Safe by construction, but the cast is unchecked from the compiler's view. - **Wrap:** map each input to a common wrapper type before racing so the result is already that type. ## Settlement, not success Like `applyToEither`, `anyOf` settles on the **first to finish**, success **or** failure. If the first future to settle throws, `anyOf` completes **exceptionally** with that exception — even if a later future would have succeeded. So 'first **successful**' is **not** what `anyOf` gives you; you'd guard each input with `exceptionally` first, or use different logic. ## The uncancelled-loser cost The biggest design consideration: the losing futures are **not cancelled**. They keep executing, holding threads, sockets, DB connections, until they finish, with their results thrown away. For a 2-way race over cheap work this is negligible; for an N-way race over expensive calls it is **N-1 units of wasted, uninterruptible work**. `CompletableFuture` has no built-in cooperative cancellation that propagates into running tasks (calling `cancel` on a `CompletableFuture` doesn't interrupt the thread running its supplier). ## Modern alternative: structured concurrency For 'first success, then cancel the rest', the cleaner tool is **structured concurrency** (`java.util.concurrent.StructuredTaskScope`, finalised in recent JDKs): `ShutdownOnSuccess` forks subtasks, returns the first successful result, and **actively cancels** (interrupts) the remaining subtasks — solving exactly the wasted-work and 'first success vs first settle' problems that `anyOf` leaves to you. A principal-level answer notes when to prefer it over `anyOf`. ## allOf vs anyOf summary | | Waits for | Returns | 2-input twin | |---|---|---|---| | `allOf` | **all** inputs | `CompletableFuture<Void>` | `thenCombine` (both) | | `anyOf` | **first** input | `CompletableFuture<Object>` | `applyToEither` (either) | ## When to reach for anyOf - Hedging / fastest-replica: race redundant backends, take the first reply, accept the small waste of the slower call. - Timeout: race the real future against `failoverAfter` or a delayed default; whichever settles first wins. - Quick prototypes where the leftover work is cheap and structured concurrency isn't available. ## Deriving your answer - Senior: 'first to settle, Object result because inputs are heterogeneous, losers not cancelled.' - Principal: weigh the wasted-work cost, the settle-vs-succeed gap, and when StructuredTaskScope.ShutdownOnSuccess is the better tool.
- Why is anyOf's result type Object while applyToEither can be a concrete type?applyToEither takes exactly two futures of the SAME type T and a Function<T,V>, so the compiler knows the result type. anyOf takes varargs of heterogeneous CompletableFuture<?>; the winner could be any input type, so the only type the API can promise is Object.
- How would you get 'the first SUCCESSFUL result and cancel the rest' rather than 'the first to settle'?Use structured concurrency: StructuredTaskScope.ShutdownOnSuccess forks the tasks, returns the first success, and actively cancels (interrupts) the remaining subtasks. With plain anyOf you'd have to guard each input with exceptionally and you still can't cancel the losers.
- Does anyOf cancel the losing futures?No. Losers keep running to completion with their results discarded, consuming threads and resources — a key cost when the work is expensive.
saying these in an interview costs you the question
- Believing anyOf returns the first SUCCESSFUL result — it returns the first to settle, including failures
- Thinking anyOf cancels the slower futures (it does not; they run on)
- Assuming you can get a typed result without a cast when inputs are heterogeneous
- Calling cancel() on a CompletableFuture and expecting it to interrupt the running supplier thread