What is Stream.mapMulti (Java 16), and when would you prefer it over flatMap?
answer
- mapMulti = Java 16 one-to-many via a Consumer, not a returned Stream
- flatMap allocates a Stream per element; mapMulti doesn't
- Push results: downstream.accept(x) zero-or-more times
- Prefer mapMulti for few results / hot paths / imperative generation
- Prefer flatMap when a stream-returning method already exists
basics
~20 smapMulti is a Java 16 stream method that, like flatMap, lets each element produce zero or more outputs — but instead of returning a stream, you push results into a consumer. It avoids creating a small stream per element, so it can be faster and allocate less when each element yields few results.
solid answer
~50 smapMulti, added in Java 16, is an imperative one-to-many stream operation. Where flatMap requires your function to return a Stream for each element, mapMulti hands your BiConsumer the element plus a downstream Consumer, and you call consumer.accept(value) zero or more times to emit results. The win is allocation: flatMap builds and tears down a fresh Stream object (and its spliterator) for every element, which is wasteful when each element maps to just a handful of values, or when those values are produced more naturally by a loop or recursion than by composing a stream. mapMulti pushes directly into the pipeline with no intermediate stream. It also reads more cleanly for cases like expanding a tree, replacing each element with a small fixed set, or filtering-plus-mapping where building a Stream.of/Stream.empty per element feels heavy. Prefer flatMap when you already have a stream-returning method (it's more declarative); prefer mapMulti for hot paths or imperative generation.
code
java · 14 lines// mapMulti (Java 16+): emit each number and its square, drop negatives
List<Integer> nums = List.of(-1, 2, 3);
List<Integer> out = nums.stream()
.<Integer>mapMulti((n, downstream) -> {
if (n < 0) return; // emit nothing -> acts as filter
downstream.accept(n); // push a result
downstream.accept(n * n); // push another
})
.collect(Collectors.toList()); // [2, 4, 3, 9]
// flatMap equivalent allocates a Stream per element:
List<Integer> out2 = nums.stream()
.flatMap(n -> n < 0 ? Stream.empty() : Stream.of(n, n * n))
.collect(Collectors.toList()); // [2, 4, 3, 9]go deeper
Likely unaware of mapMulti; that's acceptable at this level.
May recognize the name and that it's a one-to-many alternative to flatMap, without the allocation rationale.
Explains the consumer-push model and that it avoids per-element stream allocation, choosing it for few-results cases.
Reasons quantitatively about allocation/GC and parallel-split tradeoffs, knows the generic-witness gotcha, and sets a team guideline for declarative flatMap vs imperative mapMulti.
## Recap: the one-to-many operation Both `flatMap` and `mapMulti` are **one-to-many (or zero-to-many)** stream operations: each input element can produce any number of output elements. They differ in *how you express* the outputs. ## flatMap's cost model With `flatMap(Function<T,Stream<R>>)`, your function must **return a Stream** for every element. So for each element the runtime: 1. invokes your function, 2. constructs a `Stream` object (and its backing `Spliterator`), 3. drains it into the outer stream, 4. closes/discards it. When each element expands into only a **few** values, steps 2 and 4 are pure overhead — you allocate and immediately throw away a stream per element. For large inputs in hot paths, that allocation churn (and the resulting GC pressure) is measurable. ## mapMulti — pushing instead of returning Introduced in **Java 16** (`Stream.mapMulti`, plus `mapMultiToInt/Long/Double`), it inverts control. Instead of *returning* a stream, you are *given* a downstream sink and you **push** results into it: ```java // signature (simplified): <R> Stream<R> mapMulti(BiConsumer<? super T, ? super Consumer<R>> mapper); ``` Your `BiConsumer` receives the element **and** a `Consumer<R>`; call `consumer.accept(x)` **zero or more times** to emit. No per-element stream is created. ### Example — expand each number into itself and its square, dropping negatives ```java List<Integer> nums = List.of(-1, 2, 3); List<Integer> out = nums.stream() .<Integer>mapMulti((n, downstream) -> { if (n < 0) return; // emit nothing (filter) downstream.accept(n); // emit value downstream.accept(n * n); // emit square }) .collect(Collectors.toList()); // [2, 4, 3, 9] ``` The flatMap equivalent would build a `Stream.of(n, n*n)` (or `Stream.empty()`) **per element** — exactly the allocation mapMulti avoids. ## When to prefer mapMulti 1. **Few results per element** — replacing each element with a small, fixed set; allocating a stream each time is wasteful. 2. **Imperative/recursive generation** — results come more naturally from a loop or recursion (e.g. walking a tree, expanding ranges) than from composing a stream. 3. **Hot paths / allocation-sensitive code** — you want to cut per-element `Stream`/`Spliterator` allocation and GC pressure. 4. **Combined map + filter** where `Stream.of(x)` vs `Stream.empty()` branching feels heavy. ## When to prefer flatMap 1. You **already have** a stream-returning method (`List::stream`, `Files::lines`, `String::chars`-derived streams) — flatMap is more **declarative** and idiomatic. 2. Readability/composition matters more than micro-allocations. 3. You rely on flatMap's **inner-stream close guarantee** (e.g. `Files.lines`) — that lifecycle belongs to streams, not the push model. ## Caveats - mapMulti is **imperative** inside the lambda; overusing it sacrifices the declarative style streams are prized for. - The generic target type sometimes needs a **witness** (`.<R>mapMulti(...)`) because inference can't always determine `R`. - Like flatMap, it does **not** change that the operation is intermediate and lazy. ## Summary mapMulti and flatMap solve the same one-to-many problem; flatMap is the declarative, stream-returning form, while mapMulti is the imperative, push-based, allocation-light form best reserved for few-results-per-element or hot-path code.
- What does the mapMulti lambda receive, and how does it emit results?It receives the current element and a downstream Consumer; it emits by calling consumer.accept(value) zero or more times, rather than returning a stream.
- Why can mapMulti reduce allocation compared to flatMap?flatMap constructs (and discards) a Stream and Spliterator per element; mapMulti pushes values straight into the pipeline with no intermediate stream object per element.
saying these in an interview costs you the question
- Saying mapMulti returns a Stream from the lambda (it pushes to a Consumer)
- Claiming mapMulti existed before Java 16
- Asserting mapMulti is always faster — it's situational (few results/hot paths)
- Forgetting the generic witness .<R>mapMulti when inference fails