Java Stream mapMulti
Last modified: June 5, 2025
The mapMulti
method was introduced in Java 16 as a more flexible
alternative to flatMap
in the Stream API. It allows transforming
each stream element into zero or more elements by using a consumer-style approach.
This method is particularly useful when you need imperative control over element
expansion or transformation.
The mapMulti
method is a refined version of map
,
designed to convert each stream element into multiple output elements or exclude
them entirely.
Unlike flatMap
which requires returning a new stream for each input
element, mapMulti
lets you imperatively push elements to a consumer.
This can lead to more readable code in certain scenarios and better performance
by avoiding the creation of intermediate streams.
mapMulti Basics
The mapMulti
method takes a BiConsumer
that receives
each stream element and a consumer. For each input element, you can:
- Emit zero elements (filtering)
- Emit exactly one element (mapping)
- Emit multiple elements (expansion)
- Perform conditional logic before emitting
The method signature is:
<R> Stream<R> mapMulti(BiConsumer<? super T,? super Consumer<R>> mapper)
where T
is the input type and R
is the output type.
Simple Element Expansion
This example demonstrates basic element expansion using mapMulti
.
We'll convert each string in a list to its uppercase and lowercase variants.
void main() { List<String> words = List.of("apple", "banana", "cherry"); Stream<String> expanded = words.stream() .mapMulti((word, consumer) -> { consumer.accept(word.toUpperCase()); consumer.accept(word.toLowerCase()); }); expanded.forEach(System.out::println); }
The output will show each word in both uppercase and lowercase. The
mapMulti
approach here is more straightforward than using
flatMap
which would require creating a stream for each element.
This demonstrates how mapMulti
can simplify certain expansion
scenarios.
Conditional Element Emission
mapMulti
excels when you need conditional logic for element
transformation. This example filters and transforms numbers based on
multiple conditions.
void main() { Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) .mapMulti((num, consumer) -> { if (num % 2 == 0) { consumer.accept(num * 10); } if (num % 3 == 0) { consumer.accept(num * 100); } }) .forEach(System.out::println); }
This code outputs transformed numbers based on divisibility: even numbers are
multiplied by 10, numbers divisible by 3 are multiplied by 100. Some numbers
(like 6) will produce both transformations. The imperative style makes the
conditional logic clearer than the equivalent flatMap
approach.
Type Conversion with mapMulti
mapMulti
can perform type conversions while handling null values
gracefully. This example converts strings to integers, skipping invalid entries.
void main() { List<String> inputs = new ArrayList<>(); inputs.add("1"); inputs.add("2"); inputs.add("three"); inputs.add("4"); inputs.add(null); inputs.add("5"); inputs.stream() .mapMulti((str, consumer) -> { try { if (str != null) { consumer.accept(Integer.parseInt(str)); } } catch (NumberFormatException e) { // Skip invalid entries } }) .forEach(System.out::println); }
This safely converts valid strings to integers while ignoring nulls and
non-numeric strings. The try-catch block within the mapMulti
consumer provides a clean way to handle conversion errors without breaking
the stream pipeline.
Flattening a stream of strings
The mapMulti
method can also be used to flatten a stream of
strings into a single list of words. This is particularly useful when
you have a stream of comma-separated values and want to extract each word
from those strings. The following example demonstrates how to achieve this
using mapMulti
to split each line into words and flatten the
resulting arrays into a single list.
void main() { String data = """ one,two falcon,eagle spy,hunter string,number nest,tree cup,table cloud,rain war,artillery water,buck risk,gain """; List<String> res = data.lines() .map(line -> line.split(",")) // Map each line to an array of words .flatMap(Arrays::stream) // Flatten arrays into a single stream .toList(); System.out.println(res); var res2 = data.lines().<String>mapMulti((line, consumer) -> { for (var c : line.split(",")) { consumer.accept(c); } }).toList(); System.out.println(res2); }
In this example, we first split each line into an array of words using
map
, and then we flatten those arrays into a single stream using
flatMap
. The mapMulti
method achieves the same result
by directly iterating over the split words and passing each one to the consumer.
Nested Structure Flattening
mapMulti
is ideal for flattening nested data structures. This
example extracts all elements from nested lists while adding metadata.
record NestedItem(int id, List<String> values) {} void main() { List<NestedItem> items = List.of( new NestedItem(1, List.of("A", "B")), new NestedItem(2, List.of("C")), new NestedItem(3, List.of("D", "E", "F")) ); items.stream() .mapMulti((item, consumer) -> { for (String value : item.values()) { consumer.accept(item.id() + ":" + value); } }) .forEach(System.out::println); }
The output combines each nested element with its parent ID. The imperative loop
inside mapMulti
provides better control over the flattening process
compared to flatMap
, especially when you need to combine data from
different levels of the structure.
Performance Comparison
This example compares mapMulti
with flatMap
for a
simple expansion operation, demonstrating potential performance benefits.
void main() { int size = 10_000_000; long mapMultiTime = measureTime(() -> IntStream.range(0, size) .mapToObj(i -> i) .mapMulti((i, consumer) -> { consumer.accept(i * 2); consumer.accept(i * 3); }) .count()); long flatMapTime = measureTime(() -> IntStream.range(0, size) .mapToObj(i -> i) .flatMap(i -> Stream.of(i * 2, i * 3)) .count()); System.out.println("mapMulti time: " + mapMultiTime + "ms"); System.out.println("flatMap time: " + flatMapTime + "ms"); } long measureTime(Runnable operation) { long start = System.currentTimeMillis(); operation.run(); return System.currentTimeMillis() - start; }
While results vary by environment, mapMulti
often shows better
performance for simple expansions by avoiding the overhead of creating
intermediate streams. However, flatMap
may be more readable for
complex transformations, so choose based on your specific use case.
Combining with Other Operations
mapMulti
can be effectively combined with other stream operations.
This example shows filtering, transformation, and collection in one pipeline.
void main() { List<String> phrases = List.of( "Java 16", "Stream API", "mapMulti", "method", "examples"); Map<Integer, List<String>> result = phrases.stream() .<String>mapMulti((String phrase, Consumer<String> consumer) -> { String[] words = phrase.split(" "); for (String word : words) { if (word.length() > 3) { consumer.accept(word.toLowerCase()); } } }) .collect(Collectors.groupingBy(String::length)); System.out.println(result); }
This pipeline splits phrases into words, filters short words, converts to
lowercase, and groups by word length. The mapMulti
operation
handles both splitting and filtering in one step, demonstrating how it can
consolidate multiple transformations into a single operation while maintaining
readability.
Real-world Use Case
This example shows a practical application of mapMulti
for
processing hierarchical business data with conditional logic.
record Department(String name, List<Employee> employees) {} record Employee(String name, int salary, boolean active) {} void main() { List<Department> departments = List.of( new Department("Engineering", List.of( new Employee("Alice", 90000, true), new Employee("Bob", 85000, false) )), new Department("Marketing", List.of( new Employee("Carol", 80000, true) )) ); departments.stream() .mapMulti((dept, consumer) -> { if (dept.employees().size() > 1) { dept.employees().stream() .filter(Employee::active) .map(e -> dept.name() + " - " + e.name()) .forEach(consumer); } }) .forEach(System.out::println); }
This processes departments with more than one employee, filters active employees,
and creates department-employee strings. The mapMulti
approach
cleanly handles the nested conditions and transformations while maintaining good
readability. This pattern is common in business applications dealing with
hierarchical data.
Source
Java Stream mapMulti Documentation
The mapMulti
method provides a valuable addition to the Stream API,
offering imperative-style control within functional pipelines. While not a
complete replacement for flatMap
, it excels in scenarios requiring
complex conditional logic or multiple transformations per element. Choose between
them based on readability and performance requirements for your specific use case.
Author
List all Java tutorials.