Question
In Java 8, what is the difference between the Stream.map() and Stream.flatMap() methods? When should each method be used when transforming stream data?
Short Answer
map() transforms every stream element into exactly one result element. flatMap() transforms each element into a stream (or another stream-like result) and then combines those produced streams into one flat stream. By the end of this page, you will know how to choose the correct method for ordinary transformations and nested collections.
Concept
map() and flatMap() are intermediate operations in the Java Stream API. They describe how data should be transformed; the transformation runs only when a terminal operation such as collect(), count(), or forEach() consumes the stream.
map() is used for a one-to-one transformation:
- One input element produces one output element.
- The output type may change.
- The stream remains one level deep.
For example, converting each name to uppercase turns one String into one String.
flatMap() is used for a one-to-many transformation followed by flattening:
- One input element can produce zero, one, or many output elements.
- Your mapping function returns a
Stream. - Java joins all returned streams into a single stream.
For example, a stream of sentences can become a single stream of words. Each sentence produces several words, and flatMap() removes the intermediate nested streams.
This distinction matters whenever application data is nested: orders contain line items, teams contain members, files contain lines, and text contains words. flatMap() lets you process the inner values as one continuous stream.
Mental Model
Think of map() as a machine that relabels every box on a conveyor belt.
- One box enters.
- One changed box leaves.
Think of flatMap() as a machine that opens each box and places all of its contents directly onto the conveyor belt.
- One box enters.
- It may contain no items, one item, or many items.
- The individual contents leave on one flat conveyor belt.
A list of word lists illustrates the difference:
Input: [["red", "blue"], ["green"]]
map result: [Stream("red", "blue"), Stream("green")]
flatMap: ["red", "blue", "green"]
Use map() when you want transformed containers. Use flatMap() when you want the contents from those containers.
Syntax and Examples
The general method signatures are:
<R> Stream<R> map(Function<? super T, ? extends R> mapper)
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
With map(), the function returns one value:
import java.util.List;
List<String> names = List.of("ada", "linus", "grace");
List<String> upperCaseNames = names.stream()
.map(String::toUpperCase)
.toList();
System.out.println(upperCaseNames);
// [ADA, LINUS, GRACE]
String::toUpperCase receives one name and returns one name, so map() is appropriate.
With flatMap(), the function returns a stream:
import java.util.Arrays;
import java.util.List;
List<String> sentences = List.of(
"Java streams are useful",
"flatMap handles nested data"
);
List<String> words = sentences.stream()
.flatMap(sentence -> Arrays.stream(sentence.split(" ")))
.toList();
System.out.println(words);
// [Java, streams, are, useful, flatMap, handles, nested, data]
Step by Step Execution
Consider this nested list:
import java.util.List;
List<List<Integer>> rows = List.of(
List.of(1, 2),
List.of(3, 4)
);
List<Integer> values = rows.stream()
.flatMap(List::stream)
.map(number -> number * 10)
.toList();
Execution proceeds as follows:
rows.stream()creates a stream whose elements are the two inner lists:[1, 2], [3, 4]flatMap(List::stream)turns each inner list into a stream and joins them:1, 2, 3, 4map(number -> number * 10)changes each number once:10, 20, 30, 40toList()collects the final stream:[10, 20, 30, 40]
The order is important: flatMap() exposes individual numbers first, so the later map() can multiply each number.
Real World Use Cases
Common map() use cases include:
- Convert database entities into API response DTOs.
- Extract one property from each object, such as
User::getEmail. - Parse strings into numbers.
- Normalize values, such as trimming user-entered text or converting codes to uppercase.
List<String> emails = users.stream()
.map(User::getEmail)
.toList();
Common flatMap() use cases include:
- Get every line item from a collection of orders.
- Get every role assigned to a collection of users.
- Split many text records into individual words.
- Read all lines from several files when each file operation produces a stream of lines.
- Work with optional values using
Optional.stream()in Java 9 and later.
List<Product> products = orders.stream()
.flatMap(order -> order.getItems().stream())
.map(OrderItem::getProduct)
.toList();
Each order has many items. flatMap() creates one stream containing all items from every order.
Real Codebase Usage
In production code, developers often chain filter(), map(), and flatMap() to express a data-processing pipeline clearly.
Filter, flatten, then transform
List<String> activeProductNames = orders.stream()
.filter(Order::isPaid)
.flatMap(order -> order.getItems().stream())
.filter(OrderItem::isInStock)
.map(item -> item.getProduct().getName())
.toList();
This reads as a sequence of business rules:
- Keep paid orders.
- Access all their items.
- Keep in-stock items.
- Extract product names.
Avoid nested loops for simple collection traversal
Nested loops are sometimes clearer for complex logic, but flatMap() is concise when the task is simply to gather nested values.
List<String> tags = articles.stream()
.flatMap(article -> article.getTags().stream())
.distinct()
.sorted()
.toList();
Handle empty nested collections naturally
An empty inner collection produces an empty stream. No special if statement is required:
List<String> allMembers = teams.stream()
.flatMap(team -> team.getMembers().stream())
.toList();
A team with no members simply contributes no elements to allMembers. If nested data itself can be , fix the data model where possible or explicitly handle the value before calling .
Common Mistakes
Using map() when individual nested elements are needed
List<List<String>> groups = List.of(List.of("A", "B"), List.of("C"));
List<List<String>> result = groups.stream()
.map(group -> group)
.toList();
This keeps a nested list structure. To get A, B, and C as individual elements, use:
List<String> result = groups.stream()
.flatMap(List::stream)
.toList();
Returning a non-stream value from flatMap()
This does not compile because flatMap() requires a function that returns a Stream:
// Does not compile
// names.stream().flatMap(String::toUpperCase);
Use map() because toUpperCase() returns one String:
List<String> result = names.stream()
.map(String::toUpperCase)
.toList();
Comparisons
| Feature | map() | flatMap() |
|---|---|---|
| Mapping result | One value per input element | A Stream per input element |
| Output relationship | One-to-one | Zero-to-many |
| Nesting | Preserves nested results | Removes one stream level |
| Typical use | Change or extract a value | Process values inside collections, arrays, or streams |
| Example return from lambda | String, Integer, DTO | Stream<String>, Stream<Integer> |
map() can produce nested structures:
Cheat Sheet
// One input -> one output
stream.map(value -> transform(value))
// One input -> zero, one, or many outputs
stream.flatMap(value -> streamOfValues(value))
- Use
map()when the lambda returns a normal value. - Use
flatMap()when the lambda returnsStream<T>. map()can createStream<Stream<T>>orStream<List<T>>.flatMap()converts nested streams intoStream<T>.- An empty stream returned from
flatMap()contributes no output elements. flatMap()flattens one level only.- Streams are lazy until a terminal operation such as
toList(),collect(), orcount().
// Extract one field
users.stream().map(User::getName)
// Combine nested lists
teams.stream().flatMap(team -> team.getMembers().stream())
// Split text into words
sentences.stream().flatMap(s -> Arrays.stream(s.split()))
FAQ
Does flatMap() always produce more elements than map()?
No. Each input can produce zero, one, or many elements. An empty nested stream produces zero elements.
Can map() change the element type?
Yes. For example, Stream<String> can become Stream<Integer> by mapping each string to its length.
Stream<Integer> lengths = names.stream().map(String::length);
Why does flatMap() require a Stream return value?
It needs a stream to combine with the streams produced for other input elements. This is how it supports zero-to-many transformations.
Is flatMap() faster than nested loops?
Not necessarily. Choose it when the stream pipeline makes the transformation easier to read and maintain. Measure performance when it matters.
Can I use flatMap() with arrays?
Yes. Convert each array to a stream first.
Stream<String> values = arrays.stream()
.flatMap(Arrays::stream);
What is the difference between map() and ?
Mini Project
Description
Build a small order-reporting pipeline. An online shop stores orders, and each order contains several line items. Use flatMap() to gather items from every order, then use map() to turn the matching items into readable report lines.
Goal
Produce a list of formatted product lines for every item in paid orders.
Requirements
- Create
OrderandOrderItemrecords. - Store at least three orders, including one unpaid order.
- Use
filter()to keep only paid orders. - Use
flatMap()to access every item in those orders. - Use
map()to format each item as a report line. - Print the resulting list.
Keep learning
Related questions
Add External JAR Files to an IntelliJ IDEA Java Project
Learn how to add external JAR dependencies to an IntelliJ IDEA Java project using module libraries, and when to use Maven or Gradle instead.
Avoiding Java Code in JSP with JSP 2: EL and JSTL Explained
Learn how to avoid Java scriptlets in JSP 2 using Expression Language and JSTL, with examples, best practices, and common mistakes.
Call a Method After a Delay in Android Java
Learn how to run Java code after a delay in Android using Handler.postDelayed, manage the main thread, and cancel callbacks safely.