Back to Stream: flatMap
Q5Question 5 of 7

Flatten a List of departments, each holding a List of employees, into one Stream of employees using flatMap

Key Concepts
  • flatMap turns each element into a stream and then merges all those streams into one flat stream
  • department.stream().flatMap(d -> d.getEmployees().stream()) produces a single Stream<Employee> from nested lists
  • map(d -> d.getEmployees().stream()) gives Stream<Stream<Employee>>, a nested result that is not flattened
  • flatMap is a one-to-many mapping, while map is strictly one-to-one
  • The mapper you pass to flatMap must return a Stream, not a List or a plain value
  • flatMap is the standard tool for flattening nested collections and is equivalent to SelectMany in C#
  • After flattening you can collect(Collectors.toList()) to get one flat List<Employee>

Definition

flatMap maps each department to a stream of its employees and then concatenates all those streams into one flat Stream<Employee>, which map alone cannot do because map keeps the nesting.

flatMap solves the problem of nested collections by mapping and flattening in a single step.

  • flatMap(mapper) – applies a mapper that returns a Stream for each element, then merges all the returned streams into one.
  • One-to-many – each input element can produce zero, one, or many output elements, unlike map which produces exactly one.
  • Mapper returns a Stream – the function you pass must return Stream, so you call .stream() on each inner collection.
  • Flattening – the result removes one level of nesting, turning Stream> conceptually into Stream.
  • map cannot do this – map(d -> d.getEmployees().stream()) gives you Stream>, which is still nested.

At the National Bank of India, Pankaj has a filing cabinet where each drawer is a department, and inside each drawer is a folder of employee records. If he uses map, he just hands you the drawers one by one, still closed, so you end up holding a stack of drawers rather than the records. flatMap opens every drawer, takes out all the records, and lays them on one table as a single pile. Now Rajesh can process every employee in one pass without digging through drawers.

Understanding the Concepts

What is flatMap?

flatMap is a stream operation that does two things at once. For each element in the stream it applies a function that returns a new stream, and then it concatenates all those returned streams into a single stream. The result is that one level of nesting disappears.

When your data is a list of departments and each department holds a list of employees, the structure is nested. flatMap lets you walk every employee across every department as if they were one flat sequence.

JAVA
package com.bank.hr;

import java.util.List;
import java.util.stream.Collectors;

public class FlattenDemo {
    public static List<Employee> allEmployees(List<Department> departments) {
        return departments.stream()
                .flatMap(d -> d.getEmployees().stream())
                .collect(Collectors.toList());
    }
}

Each d.getEmployees().stream() is a small stream of one department's employees, and flatMap joins them into one big Stream.

How do you model the nested Department and Employee?

The department holds a list of employees. Salary uses BigDecimal because it is money.

JAVA
package com.bank.hr;

import java.math.BigDecimal;
import java.util.List;

public class Employee {
    private final String name;
    private final BigDecimal salary;

    public Employee(String name, BigDecimal salary) {
        this.name = name;
        this.salary = salary;
    }

    public String getName() { return name; }
    public BigDecimal getSalary() { return salary; }

    @Override
    public String toString() { return name; }
}

class Department {
    private final String name;
    private final List<Employee> employees;

    public Department(String name, List<Employee> employees) {
        this.name = name;
        this.employees = employees;
    }

    public String getName() { return name; }
    public List<Employee> getEmployees() { return employees; }
}

How do you run the flattening end to end?

Here is a complete program that builds two departments and flattens their employees into one list.

JAVA
package com.bank.hr;

import java.math.BigDecimal;
import java.util.List;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        Department engineering = new Department("Engineering", List.of(
                new Employee("Rajesh", new BigDecimal("120000")),
                new Employee("Anita", new BigDecimal("115000"))
        ));
        Department hr = new Department("HR", List.of(
                new Employee("Priya", new BigDecimal("95000"))
        ));

        List<Department> departments = List.of(engineering, hr);

        List<Employee> flat = departments.stream()
                .flatMap(d -> d.getEmployees().stream())
                .collect(Collectors.toList());

        System.out.println(flat);
        System.out.println("total = " + flat.size());
    }
}
TEXT
[Rajesh, Anita, Priya]
total = 3

Why can map not do this?

map is a one-to-one transform. Each element goes in, exactly one element comes out. If you map each department to a stream of its employees, each department produces one stream, so you get a stream of streams. The nesting stays.

JAVA
// WRONG: map produces Stream<Stream<Employee>>, still nested
Stream<Stream<Employee>> nested = departments.stream()
        .map(d -> d.getEmployees().stream());
// You cannot collect this into a flat List<Employee> directly.
JAVA
// CORRECT: flatMap merges the inner streams into one Stream<Employee>
List<Employee> flat = departments.stream()
        .flatMap(d -> d.getEmployees().stream())
        .collect(Collectors.toList());

The wrong version compiles but leaves you holding a Stream>, which you would have to flatten yourself. flatMap does that flattening for you.

How do map and flatMap compare?

| Aspect | map | flatMap |
|--------|-----|---------|
| Cardinality | one-to-one | one-to-many |
| Mapper returns | a value R | a Stream |
| Input Stream | Stream> | Stream |
| Flattening | no | yes, removes one nesting level |
| Typical use | change each element | expand and merge nested data |
| C# equivalent | Select | SelectMany |

The single-sentence rule: use map when each element maps to one element, and flatMap when each element maps to many and you want them merged.

How do map and flatMap look side by side?

JAVA
package com.bank.hr;

import java.math.BigDecimal;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class SideBySide {
    public static void main(String[] args) {
        List<Department> departments = List.of(
                new Department("Engineering", List.of(
                        new Employee("Rajesh", new BigDecimal("120000")))),
                new Department("HR", List.of(
                        new Employee("Priya", new BigDecimal("95000"))))
        );

        // map: one department -> one department name (one-to-one)
        List<String> names = departments.stream()
                .map(Department::getName)
                .collect(Collectors.toList());

        // flatMap: one department -> many employees (one-to-many, merged)
        List<Employee> employees = departments.stream()
                .flatMap(d -> d.getEmployees().stream())
                .collect(Collectors.toList());

        System.out.println("map result:     " + names);
        System.out.println("flatMap result: " + employees);
    }
}
TEXT
map result:     [Engineering, HR]
flatMap result: [Rajesh, Priya]

What are common mistakes with flatMap?

The most common mistake is passing a mapper that returns a collection or a value instead of a stream. flatMap requires the mapper to return a Stream.

JAVA
// WRONG: mapper returns a List, not a Stream, so this does not compile
List<Employee> flat = departments.stream()
        .flatMap(d -> d.getEmployees())   // returns List<Employee>, not Stream
        .collect(Collectors.toList());
JAVA
// CORRECT: call .stream() so the mapper returns Stream<Employee>
List<Employee> flat = departments.stream()
        .flatMap(d -> d.getEmployees().stream())
        .collect(Collectors.toList());

Another mistake is forgetting to guard against a department whose employee list could be null.

What are the edge cases?

If a department has an empty employee list, flatMap contributes nothing for it, which is correct and needs no special handling. But a null inner list throws a NullPointerException when you call .stream() on it.

JAVA
package com.bank.hr;

import java.math.BigDecimal;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class EdgeCases {
    public static void main(String[] args) {
        Department empty = new Department("Compliance", List.of());
        Department staffed = new Department("HR", List.of(
                new Employee("Priya", new BigDecimal("95000"))));

        List<Employee> flat = Stream.of(empty, staffed)
                .flatMap(d -> d.getEmployees() == null
                        ? Stream.empty()
                        : d.getEmployees().stream())
                .collect(Collectors.toList());

        System.out.println(flat); // empty department contributes nothing
    }
}
TEXT
[Priya]

Returning Stream.empty() for a null list keeps the pipeline safe.

How does flatMap work with the primitive stream variants?

There are flatMapToInt, flatMapToLong, and flatMapToDouble for when the inner stream is a primitive stream, which avoids boxing. For example, summing all salaries by flattening into a stream of amounts.

JAVA
package com.bank.hr;

import java.math.BigDecimal;
import java.util.List;

public class SumSalaries {
    public static void main(String[] args) {
        List<Department> departments = List.of(
                new Department("Engineering", List.of(
                        new Employee("Rajesh", new BigDecimal("120000")),
                        new Employee("Anita", new BigDecimal("115000")))),
                new Department("HR", List.of(
                        new Employee("Priya", new BigDecimal("95000"))))
        );

        BigDecimal total = departments.stream()
                .flatMap(d -> d.getEmployees().stream())
                .map(Employee::getSalary)
                .reduce(BigDecimal.ZERO, BigDecimal::add);

        System.out.println("total payroll = " + total);
    }
}
TEXT
total payroll = 330000

How do you flatten more than two levels?

You can chain flatMap to flatten deeper structures, for example banks that hold departments that hold employees.

JAVA
package com.bank.hr;

import java.util.List;
import java.util.stream.Collectors;

public class DeepFlatten {
    record Bank(String name, List<Department> departments) {}

    public static void main(String[] args) {
        // Each flatMap peels one level of nesting.
        List<Bank> banks = List.of();
        List<Employee> everyone = banks.stream()
                .flatMap(b -> b.departments().stream())
                .flatMap(d -> d.getEmployees().stream())
                .collect(Collectors.toList());

        System.out.println("count = " + everyone.size());
    }
}
TEXT
count = 0

Each flatMap in the chain removes exactly one level of nesting.

When should you use flatMap?

Use flatMap whenever one element naturally expands into many and you want them merged into a single stream: flattening nested lists, splitting a sentence into words, expanding an order into its line items, or turning a customer into their list of accounts. If each element maps to exactly one result, use map instead.

JAVA
package com.bank.hr;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class SplitWords {
    public static void main(String[] args) {
        List<String> lines = List.of("transfer to Priya", "loan for Rajesh");

        List<String> words = lines.stream()
                .flatMap(line -> Arrays.stream(line.split(" ")))
                .collect(Collectors.toList());

        System.out.println(words);
    }
}
TEXT
[transfer, to, Priya, loan, for, Rajesh]

What are common interview follow-up questions?

Interviewers ask you to explain why the mapper must return a Stream, to contrast flatMap with map using the Stream> result, to name the C# equivalent SelectMany, and to show Optional.flatMap which flattens nested Optionals the same way.

JAVA
package com.bank.hr;

import java.util.Optional;

public class OptionalFlatMap {
    static Optional<String> managerOf(String dept) {
        return dept.equals("HR") ? Optional.of("Priya") : Optional.empty();
    }

    public static void main(String[] args) {
        Optional<String> dept = Optional.of("HR");

        // map would give Optional<Optional<String>>; flatMap keeps it flat
        Optional<String> manager = dept.flatMap(OptionalFlatMap::managerOf);

        System.out.println(manager.orElse("none"));
    }
}
TEXT
Priya

Visual Overview

DIAGRAM
  List<Department>
       │
       │  stream()
       ▼
  ┌──────────────────────────────────────────┐
  │  Stream<Department>                        │
  │                                            │
  │  Dept(Engineering)   Dept(HR)              │
  │   [Rajesh, Anita]     [Priya]              │
  └──────────────────────────────────────────┘
       │
       │  flatMap(d -> d.getEmployees().stream())
       │
       ▼
  ┌──────────────────────────────────────────┐
  │  inner streams produced per department     │
  │   [Rajesh, Anita]      [Priya]             │
  │         │                 │                │
  │         └──────── merge ───┘                │
  └──────────────────────────────────────────┘
       │
       ▼
  Stream<Employee>  →  [Rajesh, Anita, Priya]
       │
       │  collect(toList())
       ▼
  List<Employee>
DIAGRAM
  map vs flatMap on the same input

  map(d -> d.getEmployees().stream())
        ┌─────────────────────────────┐
        │ Stream< Stream<Employee> >  │   nested, NOT flat
        │   [ [Rajesh,Anita], [Priya] ]│
        └─────────────────────────────┘

  flatMap(d -> d.getEmployees().stream())
        ┌─────────────────────────────┐
        │ Stream< Employee >          │   flat
        │   [ Rajesh, Anita, Priya ]  │
        └─────────────────────────────┘

Key Takeaway

At the National Bank of India, Pankaj keeps a filing cabinet where each drawer is a department and each drawer holds a folder of employee records. When Rajesh needs to process every employee in the bank, he does not want to open drawers one by one; he wants one flat pile of records on the table.

map would hand him the drawers still closed. Because map is a one-to-one transform, mapping each department to a stream of its employees gives a stream of streams, and the nesting stays. That is the exact reason map cannot flatten: it never merges the inner streams.

flatMap does the job in one move. For each department it takes the stream of that department's employees, and then it concatenates all those small streams into one big Stream. One level of nesting disappears. The only rule to remember is that the function you pass must return a Stream, which is why you call .stream() on each inner list.

In banking work this pattern appears constantly: a customer holding many accounts, an order holding many line items, a department holding many employees. Whenever one thing expands into many and you want them all in one sequence, flatMap is the tool, and chaining it peels deeper levels of nesting one at a time.

Remember one sentence: map keeps the nesting as a stream of streams, while flatMap maps each element to a stream and merges them into one flat stream.

---

Share
Use ← → arrow keys to navigate