ZetCode

Java Stream: flatMap vs mapMulti

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.

Key Differences Overview

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.

Basic Element Expansion

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.

FlatMapExample.java
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.

MapMultiExample.java
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.

Conditional Element Expansion

When you need to conditionally expand elements based on certain criteria, the differences between the two approaches become more apparent.

FlatMapConditional.java
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.


MapMultiConditional.java
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.

When to Use Each Approach

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.

Prefer mapMulti when:

Prefer flatMap when:

Real-world Example: Order Processing

Let's examine a more practical example processing e-commerce orders with nested items.

OrderProcessingFlatMap.java
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.

OrderProcessingMapMulti.java
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.

Migration Tips

When migrating from flatMap to mapMulti:

  1. Replace the Stream-returning function with a BiConsumer
  2. Change element emission from return Stream.of(x) to consumer.accept(x)
  3. Move filtering logic before the emission rather than using filter
  4. For empty results, simply don't call the consumer

Remember that migration isn't always necessary - flatMap remains a good choice for many scenarios, especially when working with existing Stream-returning methods.

Source

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.

Author

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.