Explainers

mapMulti(): Java Streams Just Got More Flexible

Java 16 introduced mapMulti(), a stream operation that sounds niche but could seriously streamline your code. It lets one stream element explode into zero, one, or many.

A Java code snippet illustrating the use of mapMulti() for transforming stream data.

Key Takeaways

  • mapMulti() transforms one stream element into zero, one, or many new elements.
  • It offers a more performant and readable alternative to chaining filter() and map() or using flatMap() for certain one-to-many transformations.
  • Ideal for scenarios where each input yields a small, predictable number of outputs, avoiding intermediate stream creation overhead.

So, Java 16 dropped. And with it, a little something called <a href="/tag/mapmulti/">mapMulti</a>() for the Stream API. Looks a bit odd at first glance, right? Like a Swiss Army knife with a weird extra blade. But trust me, once you get it, it’s surprisingly handy. Maybe not make coffee handy, but close.

The gist? It maps each stream item to zero, one, or more new items. All neatly bundled inside a single BiConsumer. Forget chaining filter() and map() like a madman. Or, well, maybe don’t forget entirely, but you’ll do it a lot less. Let’s see it in action.

Picture this: student grades. You want the ones above a 7, then tack on a 10% bonus. The old way? Clunky.

The Classic (and slightly tiresome) Dance:

List<Double> notas = List.of(5.5, 8.0, 6.0, 9.5, 7.0, 4.0);
List<Double> notasComBonus = notas.stream()
.filter(n -> n >= 7)
.map(n -> n * 1.10)
.collect(Collectors.toList());
// Result: [8.8, 10.45, 7.7]

Now, with mapMulti(), it’s a one-act play.

List<Double> notasComBonusMM = notas.stream()
.<Double>mapMulti((nota, consumer) -> {
if (nota >= 7) {
consumer.accept(nota * 1.10);
}
})
.collect(Collectors.toList());

See that? The if handles the filtering. The accept() does the mapping. Done. It’s cleaner. That .<Double>? A type witness the compiler demands. A small price for this kind of power.

Need to sum those bonus-enhanced grades? Java’s got a specialized version: mapMultiToDouble().

double somaComBonus = notas.stream()
.mapMultiToDouble((nota, consumer) -> {
if (nota >= 7) {
consumer.accept(nota * 1.10);
}
})
.sum();
System.out.println(somaComBonus); // 26.95

No type witness needed here. Slick. If you need to shift back to a Stream<Double>, boxed() or mapToObj() are your friends.

Let’s get real. Product categories, each with a list of products. You need a flat list: ProdutoInfo objects, each containing the category name and product name.

class Categoria {
private String nome;
private List<Produto> produtos;
// getters...
}
class Produto {
private String nome;
private double preco;
// getters...
}
class ProdutoInfo {
private String categoria;
private String produto;
// constructor, getters...
}

The typical flatMap approach:

List<ProdutoInfo> listaFlat = categorias.stream()
.flatMap(cat -> cat.getProdutos().stream()
.map(prod -> new ProdutoInfo(cat.getNome(), prod.getNome()))
)
.collect(Collectors.toList());

Here’s the rub: for every category, you spin up a new intermediate stream (cat.getProdutos().stream()). If you’ve got a truckload of categories, that’s overhead.

mapMulti() enters the chat:

List<ProdutoInfo> listaMM = categorias.stream()
.<ProdutoInfo>mapMulti((categoria, consumer) -> {
for (Produto p : categoria.getProdutos()) {
consumer.accept(new ProdutoInfo(categoria.getNome(), p.getNome()));
}
})
.collect(Collectors.toList());

No intermediate streams. Just a plain for loop inside. More performant? Absolutely. Easier to read? In my humble — and correct — opinion, yes.

Want only products over $50? mapMulti() shines even brighter.

List<ProdutoInfo> listaCaros = categorias.stream()
.<ProdutoInfo>mapMulti((categoria, consumer) -> {
for (Produto p : categoria.getProdutos()) {
if (p.getPreco() > 50) {
consumer.accept(new ProdutoInfo(categoria.getNome(), p.getNome()));
}
}
})
.collect(Collectors.toList());

You’re skipping an explicit filter() step. It’s all contained. And since most categories have few products, it fits the official guidance perfectly: use mapMulti() when you expect to transform each stream element into a small, finite number (zero included) of new elements.

Got a method that does all the heavy lifting and just needs a Consumer? Pass it straight into mapMulti().

Add this to your Categoria class:

public void produtosCaros(Consumer<ProdutoInfo> consumer, double limite) {
for (Produto p : this.produtos) {
if (p.getPreco() > limite) {
consumer.accept(new ProdutoInfo(this.nome, p.getNome()));
}
}
}

Your stream becomes… pristine.

List<ProdutoInfo> resultado = categorias.stream()
.<ProdutoInfo>mapMulti(cat -> cat.produtosCaros(consumer, 50))
.collect(Collectors.toList());

Method references make it even prettier. (Okay, slight adaptation needed, but the concept holds: mapMulti accepts a lambda that calls your imperative method).

The Case for mapMulti()

  • You’re dealing with a one-to-zero/one-to-many relationship where the number of generated elements per original element is small.
  • You want to avoid creating intermediate streams, boosting performance in loop-heavy scenarios.
  • Your logic for transforming and potentially filtering is tightly coupled, and keeping it in one place simplifies the code.

The old guard might scoff. “Why not just use flatMap()?” they’ll cry. Well, flatMap is for turning one element into a stream of elements. mapMulti is for turning one element into zero or more elements directly, without the overhead of creating those tiny, fleeting intermediate streams. It’s a subtle but important distinction for performance and, dare I say, code clarity. This isn’t some grand paradigm shift. It’s an optimization. A refinement. And those matter.

Why This Matters for Developers

Look, Java’s Stream API has always been about expressing complex data processing pipelines declaratively. mapMulti() is another tool in that box, specifically for those situations where a single input might legitimately produce multiple outputs. Think parsing lines of a file where one line could yield multiple records, or processing events that might trigger several sub-events. Before mapMulti(), you’d often contort your logic, perhaps creating temporary lists or resorting to more verbose flatMap implementations that might feel… heavyweight. This new method offers a more direct, efficient pathway. It’s less about replacing existing patterns and more about providing a cleaner, more performant alternative for a specific class of problems. It signals a continued commitment to refining the developer experience within the Java ecosystem.

Is mapMulti() Really Better Than flatMap()?

It’s not about “better” in an absolute sense. It’s about “better suited.” flatMap() excels when you need to convert a single element into a stream of other elements. This is common for things like getting all words from a sentence stream, or all characters from a string stream. mapMulti() is designed for when a single element directly produces zero or more concrete elements, not necessarily a full stream. The key difference is the overhead of stream creation. For scenarios where each input yields a small, fixed number of outputs, mapMulti() avoids the cost of spinning up and tearing down those mini-streams, making it more efficient and often more readable. Think of it as a specialized tool for a specific job where flatMap() is the more general-purpose hammer.


🧬 Related Insights

Frequently Asked Questions

What does mapMulti() actually do? mapMulti() is a Java Stream API method introduced in JDK 16 that allows you to transform one element from a stream into zero, one, or multiple elements. It takes a BiConsumer where the first argument is the stream element and the second is a Consumer to accept the new elements generated.

Will this replace my job? No. mapMulti() is a tool for streamlining code, not replacing developers. It simplifies certain stream operations, making code more efficient and readable for specific tasks.

When should I use mapMulti() instead of flatMap()? Use mapMulti() when each input element naturally produces a small, fixed number of output elements (zero, one, or a few). Use flatMap() when each input element should be converted into a potentially larger, dynamic stream of elements. mapMulti() avoids the overhead of creating intermediate streams.

Written by
DevTools Feed Editorial Team

Curated insights, explainers, and analysis from the editorial team.

Frequently asked questions

What does mapMulti() actually do?
`mapMulti()` is a Java Stream API method introduced in <a href="/tag/jdk-16/">JDK 16</a> that allows you to transform one element from a stream into zero, one, or multiple elements. It takes a `BiConsumer` where the first argument is the stream element and the second is a `Consumer` to accept the new elements generated.
Will this replace my job?
No. `mapMulti()` is a tool for streamlining code, not replacing developers. It simplifies certain stream operations, making code more efficient and readable for specific tasks.
When should I use mapMulti() instead of flatMap()?
Use `mapMulti()` when each input element naturally produces a small, fixed number of output elements (zero, one, or a few). Use `flatMap()` when each input element should be converted into a potentially larger, dynamic *stream* of elements. `mapMulti()` avoids the overhead of creating intermediate streams.

Worth sharing?

Get the best Developer Tools stories of the week in your inbox — no noise, no spam.

Originally reported by dev.to

Stay in the loop

The week's most important stories from DevTools Feed, delivered once a week.