flatMap()

Turn each element into a stream of zero or more values and join them into one flat stream, such as all line items across a list of orders.

Signature
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
  • Added in Java Java 8

Parameters

ParameterTypeDescription
$mapperFunction<? super T, ? extends Stream<? extends R>>A non-interfering, stateless function that returns a stream for each element. Returning null is treated as an empty stream. Each returned stream is closed after its contents have been placed into the result.

Return value

Stream<R>A new stream containing the elements of every stream the mapper returned, joined in encounter order.

Errors

Nothing directly. Exceptions thrown by the mapper surface when the terminal operation runs. Passing a null mapper throws NullPointerException.

Changelog

  • PHP Java 10flatMap now respects short-circuiting: operations such as findFirst and limit stop pulling from an inner stream once they have enough elements, so an infinite inner stream no longer prevents the pipeline from finishing.

flatMap is map for functions that return several values. The mapper returns a Stream for each element, and flatMap joins all of those streams into one, in order. An element that maps to an empty stream simply disappears.

map versus flatMap

With an Order that holds a List<Line>, orders.stream().map(Order::lines) gives a Stream<List<Line>>: still one item per order. orders.stream().flatMap(o -> o.lines().stream()) gives a Stream<Line> with every line of every order. A rule of thumb: if the result type you get is a stream or list of lists, you wanted flatMap.

Common shapes

  • Nested collections: flatMap(o -> o.lines().stream()) or flatMap(List::stream) for a list of lists.
  • Splitting text: flatMap(s -> Arrays.stream(s.split(" "))) turns lines into words or a scope string into separate scopes.
  • Optional results: map(this::lookup).flatMap(Optional::stream) keeps only the lookups that found something. Optional.stream() arrived in Java 9.
  • Nullable values: flatMap(Stream::ofNullable) drops nulls.

Primitive versions and mapMulti

flatMapToInt, flatMapToLong and flatMapToDouble return primitive streams. When each element yields only a few values, creating a small stream per element has some overhead; mapMulti (Java 16) pushes values into a consumer instead and avoids those streams. For most service code the difference does not matter, so pick whichever reads better.

Laziness and short-circuiting

Since Java 10, flatMap respects short-circuiting operations. findFirst() or limit(n) stop pulling from an inner stream as soon as they have enough, so even an infinite inner stream works. Before that change, each inner stream was pushed through completely, which is why older answers online warn that flatMap is not lazy.

Back-end notes

Flattening a list of parents into their children is the in-memory version of a SQL join. When the children come from lazy-loaded database relations, calling o.lines() inside flatMap can issue one query per order (the N+1 problem). Fetch the children in the original query, or load them in one batch by parent ID, and then flatten.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Collect every line item across a page of orders to compute stock reservations in one pass.
  • Expand space-separated OAuth scopes from several grants into one de-duplicated list for an authorisation check.
  • Resolve a batch of IDs through a cache that returns Optional and keep only the hits.

Frequently asked questions

How do I flatten a List<List<T>> into a List<T> in Java?

Call listOfLists.stream().flatMap(List::stream).toList(). Each inner list becomes a stream and flatMap joins them in order.

When should I use flatMap instead of map?

Use flatMap when the function returns a Stream, a collection or an Optional and you want its contents rather than the container. If your result type comes out as Stream<List<X>> or List<List<X>>, switch to flatMap.

Is Stream.flatMap lazy?

Since Java 10 it respects short-circuiting, so findFirst, anyMatch and limit stop reading an inner stream once they have their answer. In Java 8 and 9, each inner stream was consumed in full before the next step, which could hang with an infinite inner stream.

More examples

Splitting space-separated scopes

OAuth-style scope strings are space-separated, so each grant expands into several scopes. distinct and sorted then give a clean, stable list for an access check or a token response.

Example · java
void main() {
    List<String> grants = List.of("read:users write:users", "read:orders", "read:users");

    List<String> scopes = grants.stream()
        .flatMap(g -> Arrays.stream(g.split(" ")))
        .distinct()
        .sorted()
        .toList();
    IO.println(scopes);
}
// Output:
// [read:orders, read:users, write:users]

Keeping only the lookups that found something

Optional.stream() is a stream of zero or one element, so flatMap drops the empty results and unwraps the rest. This replaces the older filter(Optional::isPresent).map(Optional::get) pair.

Example · java
Optional<String> findEmail(int userId) {
    return userId % 2 == 0
        ? Optional.of("user" + userId + "@example.com")
        : Optional.empty();
}

void main() {
    List<String> emails = Stream.of(1, 2, 3, 4)
        .map(id -> findEmail(id))
        .flatMap(Optional::stream)
        .toList();
    IO.println(emails);
}
// Output:
// [[email protected], [email protected]]

Short-circuiting through an infinite inner stream

Each inner stream is infinite, yet both pipelines finish, because since Java 10 flatMap stops pulling from the inner stream once findFirst or limit is satisfied. The second outer element is never expanded.

Example · java
void main() {
    Optional<Integer> first = Stream.of(1, 2)
        .flatMap(n -> Stream.iterate(n * 100, i -> i + 1))
        .findFirst();
    IO.println(first.get());

    List<Integer> firstThree = Stream.of(1, 2)
        .flatMap(n -> Stream.iterate(n * 100, i -> i + 1))
        .limit(3)
        .toList();
    IO.println(firstThree);
}
// Output:
// 100
// [100, 101, 102]

Discussion

  • Be the first to comment on this lesson.