ZetCode

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:

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.

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

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

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

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

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

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

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

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

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.