CodeGym /Courses /JAVA 25 SELF /flatMap and mapMulti methods

flatMap and mapMulti methods

JAVA 25 SELF
Level 32, Lesson 0
Available

1. Introduction

Let’s imagine we have a list of students, and each student has a list of hobbies. For example:

List<List<String>> hobbies = List.of(
    List.of("Swimming", "Chess"),
    List.of("Soccer"),
    List.of("Programming", "Reading", "Movies")
);

Your task: get a single list of all hobbies to find out what students are interested in. It’s logical to try using map:

List<Stream<String>> streams = hobbies.stream()
    .map(list -> list.stream())
    .collect(Collectors.toList());

What did we get? A list of streams! That is, Stream<Stream<String>>. That’s not quite what we need — we’d like to get just a Stream<String> to work with each hobby directly.

Imagine you have a box containing other boxes with toys. The map method just takes out each box and shows you the box (Stream<Stream<String>>). But you want to see all the toys at once (Stream<String>) without digging into boxes.

2. flatMap: how to flatten nested collections

To “unpack the boxes” and get a flat stream, you need the flatMap method.
It takes a function that returns a stream for each element and immediately “flattens” all nested streams into one.

flatMap syntax

Stream<T> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)

Simply put: flatMap expects you to return a stream (Stream) for each element, and it will “smooth” all of that into one large stream.

Example: merging all student hobbies

List<String> allHobbies = hobbies.stream()
    .flatMap(list -> list.stream())
    .collect(Collectors.toList());

System.out.println(allHobbies);
// [Swimming, Chess, Soccer, Programming, Reading, Movies]

For each list of hobbies, we called list.stream() (we got a stream of hobbies for one student). And then flatMap “smoothed” all streams into one big stream — now we see all hobbies, not streams of hobbies.

Visual scheme

hobbies.stream()
    |
    |---> [Swimming, Chess]      -> stream
    |---> [Soccer]               -> stream
    |---> [Programming, ...]     -> stream
    |
flatMap: combines everything into a single Stream<String>

Why doesn’t map solve the task?

If we used map, we’d get Stream<Stream<String>>, which is inconvenient: for example, you cannot immediately iterate over all strings — you’d need extra passes.

3. Practical examples of using flatMap

Splitting strings into characters (List<String> → Stream<Character>)

Suppose you have a list of strings, and you want to get a stream of all characters that appear across all strings:

List<String> words = List.of("Java", "Stream");

List<Character> characters = words.stream()
    .flatMap(word -> word.chars().mapToObj(ch -> (char) ch))
    .collect(Collectors.toList());

System.out.println(characters);
// [J, a, v, a, S, t, r, e, a, m]

Explanation:

  • word.chars() returns an IntStream (a stream of code points).
  • mapToObj turns code points into characters.
  • flatMap merges all resulting character streams into one.

Working with Optional: Stream<Optional<T>> → Stream<T>

Suppose you have a list of Optional objects, and you want to get a stream of only those values that are actually present:

List<Optional<String>> optionals = List.of(
    Optional.of("Java"),
    Optional.empty(),
    Optional.of("Stream")
);

List<String> present = optionals.stream()
    .flatMap(opt -> opt.stream())
    .collect(Collectors.toList());

System.out.println(present);
// [Java, Stream]

Tip:
Optional<T> (since Java 9) has a stream() method that returns either an empty stream or a stream with a single element. flatMap combines all non-empty values into one stream.

4. The new mapMulti method: when flatMap isn’t needed

Why was mapMulti introduced?

Introduced in Java 16, mapMulti works similarly to flatMap but is a bit more efficient and flexible.

flatMap is a powerful tool, but it has a drawback: for each element you are required to create a new Stream, even if you want to return 0, 1, or several elements. This can be inefficient, especially if you just want to “expand” elements without creating intermediate collections or streams.

mapMulti is an improved version of flatMap that lets you add values directly to the resulting stream via a Consumer, without creating intermediate structures. Instead of returning a stream, you specify which elements to add to the resulting stream via the Consumer.

mapMulti syntax

<R> Stream<R> mapMulti(BiConsumer<? super T, ? super Consumer<R>> mapper)

For each element, the mapper function is called, receiving the element itself and a Consumer into which you can “put” new values.

Example: filtering and expanding elements in a single pass

Suppose you have a list of numbers, and you want to add even numbers to the stream twice, and odd numbers not at all (i.e., filtering and “expanding” at the same time):

List<Integer> numbers = List.of(1, 2, 3, 4);

List<Integer> result = numbers.stream()
    .mapMulti((number, consumer) -> {
        if (number % 2 == 0) {
            consumer.accept(number);
            consumer.accept(number); // Add twice
        }
        // Do nothing for odd numbers (filtering)
    })
    .collect(Collectors.toList());

System.out.println(result);
// [2, 2, 4, 4]

Comparison with flatMap

The same task with flatMap would look like this:

List<Integer> result = numbers.stream()
    .flatMap(number -> number % 2 == 0
        ? Stream.of(number, number)
        : Stream.empty())
    .collect(Collectors.toList());

Here we still have to create Stream.of(...) or Stream.empty() for each element, even if it’s inefficient.

5. When to use flatMap vs mapMulti?

  • map — when you transform each element into a single element.
  • flatMap — when you transform each element into a stream of elements.
  • mapMulti — when you want to generate multiple elements without creating an intermediate stream (more efficient, especially in hot loops).

Another example: flattening Map<Integer, List<String>> into Stream<Pair(Integer, String)>

Suppose we have a map: id → list of hobbies.

Map<Integer, List<String>> studentHobbies = Map.of(
    1, List.of("Swimming", "Chess"),
    2, List.of("Soccer"),
    3, List.of("Programming", "Reading", "Movies")
);

List<String> allHobbies = studentHobbies.values().stream()
    .flatMap(Collection::stream)
    .collect(Collectors.toList());

System.out.println(allHobbies);
// [Swimming, Chess, Soccer, Programming, Reading, Movies]

And if we want to get pairs (id, hobby):

List<String> pairs = studentHobbies.entrySet().stream()
    .flatMap(entry -> entry.getValue().stream()
        .map(hobby -> entry.getKey() + ": " + hobby))
    .collect(Collectors.toList());

System.out.println(pairs);
// [1: Swimming, 1: Chess, 2: Soccer, 3: Programming, 3: Reading, 3: Movies]

Using mapMulti (Java 16+):

List<String> pairs = studentHobbies.entrySet().stream()
    .mapMulti((entry, consumer) -> {
        for (String hobby : entry.getValue()) {
            consumer.accept(entry.getKey() + ": " + hobby);
        }
    })
    .collect(Collectors.toList());

Advantage:
mapMulti doesn’t create intermediate streams; it simply pushes values directly into the resulting stream.

Visualization: flatMap and mapMulti

Method What the function returns How it combines Intermediate collections?
map
Single element Simple No
flatMap
A stream (Stream) Concatenates Yes (intermediate Streams)
mapMulti
Consumer (0..n times) Concatenates No (adds directly)

6. Common mistakes when working with flatMap and mapMulti

Error #1: Got Stream<Stream<T>> instead of Stream<T>. Students often use map instead of flatMap when working with collections of collections. As a result, they have to write extra loops.

Error #2: Incorrect return type. For flatMap, the function must return a Stream, not a List or an array.

Error #3: Inefficiency. In simple cases, flatMap works great, but if you have to create Stream.of or Stream.empty() for each element, that can be excessive. For such tasks, it’s better to use mapMulti.

Error #4: mapMulti doesn’t work in older Java versions. mapMulti appeared only in Java 16. If your JDK version is older, this method won’t be available.

Error #5: Issues with null. Do not return null from flatMap — always return Stream.empty() for “empty” cases.

Error #6: Intermediate collections. Don’t create extra lists or streams if you can add elements directly via a consumer in mapMulti.

1
Task
JAVA 25 SELF, level 32, lesson 0
Locked
Collecting all student hobbies for school clubs 📚
Collecting all student hobbies for school clubs 📚
1
Task
JAVA 25 SELF, level 32, lesson 0
Locked
Creating a hobby catalog for each student 🧑‍🎓
Creating a hobby catalog for each student 🧑‍🎓
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION