What is the difference between Stream.map and Stream.flatMap in Java?
answer
- map = one-to-one, same count
- flatMap = one-to-many + flatten
- Stream<Stream<X>> signals flatMap
- flatMap function returns a Stream, map's returns a value
- flatMap can drop to zero (map+filter)
basics
~20 smap transforms each element into exactly one new element (one-to-one). flatMap turns each element into a stream of elements and merges them all into one flat stream (one-to-many), so you end up with more or fewer elements, not the same count.
solid answer
~40 smap applies a function to every element and produces exactly one output per input, keeping the stream the same size: Stream<T> becomes Stream<R>. flatMap is for when each element expands into several (or zero) values: its function returns a Stream for each element, and flatMap concatenates all those sub-streams into a single flat stream. The classic use is flattening nested structures, like turning a List<List<String>> into a Stream<String>, or splitting each sentence into its words. A simple rule: if your mapping function would otherwise hand you a Stream<Stream<X>> (a stream of streams), use flatMap instead of map to collapse it to Stream<X>. So map preserves cardinality; flatMap changes it because it both maps and flattens in one step.
go deeper
Knows map is one-to-one and flatMap flattens nested collections; can give the List<List<X>> example.
Articulates the cardinality difference precisely, recognizes the Stream<Stream<X>> smell, and knows flatMap's function returns a Stream.
Explains flatMap as map+flatten in one step, notes it can also act as a filter (zero-output), and contrasts with Optional.map/flatMap.
Frames map/flatMap as the functor map vs monadic bind pattern across Stream/Optional/CompletableFuture and discusses cost/allocation tradeoffs and mapMulti.
## Background: what a Stream is A **Stream** in Java (`java.util.stream.Stream`) is a sequence of elements you process with a pipeline of operations, e.g. `list.stream().filter(...).map(...).collect(...)`. Operations are either **intermediate** (lazy, return another stream — like `map` and `flatMap`) or **terminal** (trigger execution — like `collect`, `forEach`, `count`). ## map — one-to-one transformation `map` takes a `Function<T,R>` and applies it to **each** element, producing **exactly one** output per input. The number of elements is unchanged (cardinality is preserved); only their type/value changes. ```java Stream.of("a","bb","ccc") .map(String::length) // Function<String,Integer> .forEach(System.out::println); // 1, 2, 3 (3 in -> 3 out) ``` Think of `map` as `Stream<T> -> Stream<R>`. ## The problem map can't solve Suppose each element should expand into **several** values. Example: a `List<List<Integer>>` and you want every inner integer, or each sentence split into words. If you call `map`, the mapping function itself returns a collection or a stream, so you get a **stream of streams** (or a stream of lists): `Stream<Stream<Integer>>` or `Stream<List<Integer>>`. That nesting is awkward — you can't directly `sum()` or iterate the integers. ```java List<List<Integer>> nested = List.of(List.of(1,2), List.of(3,4)); nested.stream() .map(List::stream) // Stream<Stream<Integer>> -- nested! not what we want ... ``` ## flatMap — one-to-many + flatten `flatMap` takes a `Function<T, Stream<R>>`: for each element it returns a **whole stream** of zero, one, or many results. `flatMap` then **concatenates** (flattens) all those sub-streams into a single, flat `Stream<R>`. ```java List<List<Integer>> nested = List.of(List.of(1,2), List.of(3,4)); int sum = nested.stream() .flatMap(List::stream) // Stream<Integer>: 1,2,3,4 -- flat! .mapToInt(Integer::intValue) .sum(); // 10 ``` Because an element can map to **zero** items, `flatMap` can also shrink the stream (it acts as a combined map + filter + concat). ## The mental rule - Mapping function returns **one value** -> use `map` (cardinality preserved). - Mapping function returns **a stream/collection of values** (and you want them flattened) -> use `flatMap` (cardinality changes; nesting removed). - If you ever find yourself with `Stream<Stream<X>>` or `Stream<List<X>>` and wish it were `Stream<X>`, that's the `flatMap` signal. ## Related variants - Primitive forms exist: `mapToInt`, `flatMapToInt`, etc. - `Optional` also has `map`/`flatMap` with the same idea (`flatMap` avoids `Optional<Optional<X>>`). - Since Java 16 there is `mapMulti`, an imperative alternative that pushes results into a consumer rather than creating a stream per element (covered in a separate question).
- What type does the function passed to flatMap return?A Stream (or in primitive forms an IntStream/LongStream/DoubleStream) of the result type — flatMap then concatenates all those streams into one.
- Can flatMap reduce the number of elements in a stream?Yes. An element may map to an empty stream, contributing zero output elements, so flatMap can act like a combined map and filter.
saying these in an interview costs you the question
- Saying flatMap and map are interchangeable
- Claiming flatMap keeps the same number of elements
- Thinking flatMap's function returns a single value rather than a stream
- Confusing flatten with sorting or distinct