Question

Java 8 Stream map() vs flatMap(): Differences, Examples, and Uses

javajava-8java-stream

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.

Take Quiz

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]

Each sentence becomes a Stream<String> of words. flatMap() combines those word streams into one Stream<String>.

If you used map() in the second example, the result would be a Stream<Stream<String>>, not a stream of individual words:

var wordStreams = sentences.stream()
    .map(sentence -> Arrays.stream(sentence.split(" ")));

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:

  1. rows.stream() creates a stream whose elements are the two inner lists:
    [1, 2], [3, 4]
    
  2. flatMap(List::stream) turns each inner list into a stream and joins them:
    1, 2, 3, 4
    
  3. map(number -> number * 10) changes each number once:
    10, 20, 30, 40
    
  4. toList() 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.

Take Quiz

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:

  1. Keep paid orders.
  2. Access all their items.
  3. Keep in-stock items.
  4. 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 null, fix the data model where possible or explicitly handle the null value before calling .stream().

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();

Forgetting to flatten after splitting strings

List<String[]> parts = sentences.stream()
    .map(sentence -> sentence.split(" "))
    .toList();

This produces a list of arrays. To produce a list of words, convert each array to a stream and flatten it:

List<String> words = sentences.stream()
    .flatMap(sentence -> Arrays.stream(sentence.split(" ")))
    .toList();

Calling .stream() on a null collection

// Throws NullPointerException if getItems() returns null
.flatMap(order -> order.getItems().stream())

Prefer collections that are never null; return an empty list when there are no items. If that is not possible, handle the missing value deliberately.

Reusing a consumed stream

A stream can be consumed only once:

Stream<String> stream = List.of("a", "b").stream();
stream.toList();
// stream.count(); // IllegalStateException

Create a new stream from the source collection for another operation.

Comparisons

Featuremap()flatMap()
Mapping resultOne value per input elementA Stream per input element
Output relationshipOne-to-oneZero-to-many
NestingPreserves nested resultsRemoves one stream level
Typical useChange or extract a valueProcess values inside collections, arrays, or streams
Example return from lambdaString, Integer, DTOStream<String>, Stream<Integer>

map() can produce nested structures:

Stream<List<String>> lists = groups.stream().map(group -> group);

flatMap() removes one level of nesting:

Stream<String> names = groups.stream().flatMap(List::stream);

flatMap() does not recursively flatten every possible level. It flattens one level at a time. For deeply nested data, additional transformations or a different data model may be needed.

Take Quiz

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 returns Stream<T>.
  • map() can create Stream<Stream<T>> or Stream<List<T>>.
  • flatMap() converts nested streams into Stream<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(), or count().
// 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 filter()?

map() transforms values. filter() keeps or removes values based on a boolean condition. They are often used together.

Does flatMap() remove duplicate values?

No. Use distinct() after flatMap() if duplicates should be removed.

List<String> uniqueTags = articles.stream()
    .flatMap(article -> article.getTags().stream())
    .distinct()
    .toList();
Take Quiz

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 Order and OrderItem records.
  • 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.
Take Quiz

Keep learning

Related questions

View more