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.