Last modified: June 8, 2025
When working with Java Streams, developers often need to transform and expand
stream elements. Java provides two primary methods for this:
flatMap (since Java 8) and mapMulti (introduced in
Java 16). This tutorial explores their differences, performance characteristics,
and ideal use cases.
While both methods can transform each stream element into zero or more output
elements, they follow different paradigms. flatMap uses a
functional approach requiring new Stream instances, while mapMulti
uses an imperative consumer-based approach that can be more efficient in certain
scenarios.
The following table summarizes the key differences between
mapMulti and flatMap in Java Streams.
| Feature | mapMulti | flatMap |
|---|---|---|
| Introduced | Java 16 | Java 8 |
| Programming Style | Imperative (push) | Functional (pull) |
| Performance | Better for simple expansions | Better for complex transformations |
| Intermediate Objects | No intermediate streams | Creates intermediate streams |
| Readability | Better for imperative logic | Better for functional pipelines |
Both methods can achieve similar results, but their performance and readability can vary significantly based on the specific use case. Understanding these differences helps you choose the right tool for your stream processing needs.
This example shows how both methods can expand each input element into multiple output elements. We'll convert strings to their uppercase and lowercase variants.
void main() {
List<String> words = List.of("apple", "banana", "cherry");
List<String> result = words.stream()
.flatMap(word -> Stream.of(word.toUpperCase(), word.toLowerCase()))
.toList();
System.out.println(result);
}
This code uses flatMap to transform each word into two
elements: its uppercase and lowercase versions. The flatMap
method takes a function that returns a Stream for each input element, which
is then flattened into a single output stream.
The mapMulti method uses a BiConsumer to emit multiple
elements for each input. The consumer accepts the transformed elements directly,
avoiding the need to create intermediate Stream instances.
void main() {
List<String> words = List.of("apple", "banana", "cherry");
List<String> result = words.stream()
.<String>mapMulti((word, consumer) -> {
consumer.accept(word.toUpperCase());
consumer.accept(word.toLowerCase());
})
.toList();
System.out.println(result);
}
Both versions produce identical output, but mapMulti avoids
creating intermediate Stream instances. For simple expansions like this,
mapMulti often shows better performance while maintaining
readability.
When you need to conditionally expand elements based on certain criteria, the differences between the two approaches become more apparent.
void main() {
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6);
List<Integer> result = numbers.stream()
.flatMap(n -> {
if (n % 2 == 0) {
return Stream.of(n, n * 10);
}
return Stream.empty();
})
.toList();
System.out.println(result);
}
This code uses flatMap to transform even numbers into two
elements: the number itself and its tenfold. If the number is odd, it returns
an empty Stream. This approach works, but it can lead to less readable code
when dealing with multiple conditions or complex logic.
void main() {
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6);
List<Integer> result = numbers.stream()
.<Integer>mapMulti((n, consumer) -> {
if (n % 2 == 0) {
consumer.accept(n);
consumer.accept(n * 10);
}
})
.toList();
System.out.println(result);
}
The mapMulti version handles conditional logic more naturally
without requiring explicit empty stream returns. This makes the code more
straightforward when dealing with multiple conditions or complex branching
logic.
The maptMulti and flatMap methods each have their
strengths and ideal use cases. Understanding when to use each can help you
write more efficient and readable code.
The mapMulti method has several advantages over
flatMap in certain scenarios:
The mapMulti method is particularly useful when you need to
perform simple expansions (1-to-few elements) or when you want to handle
complex conditional logic in a more imperative style. It allows you to
emit elements directly without creating intermediate Stream objects, which can
improve performance in certain scenarios.
Let's examine a more practical example processing e-commerce orders with nested items.
record Order(String id, List<Item> items) {}
record Item(String sku, int quantity, double price) {}
void main() {
List<Order> orders = List.of(
new Order("O1", List.of(
new Item("I1", 2, 9.99),
new Item("I2", 1, 19.99)
)),
new Order("O2", List.of(
new Item("I3", 5, 4.99)
))
);
List<String> result = orders.stream()
.flatMap(order ->
order.items().stream()
.filter(item -> item.quantity() > 1)
.map(item -> "%s: %s x %.2f".formatted(
order.id(), item.sku(), item.price() * item.quantity()
))
)
.toList();
System.out.println(result);
}
This code uses flatMap to transform each word into two
elements: its uppercase and lowercase versions. The flatMap
method takes a function that returns a Stream for each input element, which
is then flattened into a single output stream.
record Order(String id, List<Item> items) {}
record Item(String sku, int quantity, double price) {}
void main() {
List<Order> orders = List.of(
new Order("O1", List.of(
new Item("I1", 2, 9.99),
new Item("I2", 1, 19.99)
)),
new Order("O2", List.of(
new Item("I3", 5, 4.99)
))
);
List<String> result = orders.stream()
.<String>mapMulti((order, consumer) -> {
for (Item item : order.items()) {
if (item.quantity() > 1) {
String line = "%s: %s x %.2f".formatted(
order.id(), item.sku(), item.price() * item.quantity()
);
consumer.accept(line);
}
}
})
.toList();
System.out.println(result);
}
In this real-world scenario, both approaches work well. The flatMap
version may be preferable when already working with methods that return Streams,
while mapMulti offers better performance and more straightforward
imperative logic when dealing with complex conditions.
When migrating from flatMap to mapMulti:
return Stream.of(x) to consumer.accept(x)filter
Remember that migration isn't always necessary - flatMap remains a
good choice for many scenarios, especially when working with existing
Stream-returning methods.
Java Stream mapMulti Documentation
Java Stream flatMap Documentation
Both mapMulti and flatMap are valuable tools in the
Stream API. mapMulti provides an imperative alternative that can be
more efficient for certain patterns, while flatMap remains the more
functional approach. Choose based on your specific requirements, coding style,
and performance needs.
My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.
List all Java tutorials.