Definition
flatMap solves the problem of nested collections by mapping and flattening in a single step.
- flatMap(mapper) – applies a mapper that returns a
Streamfor 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
mapwhich 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
Streamconceptually into- >
Stream. - map cannot do this –
map(d -> d.getEmployees().stream())gives youStream, 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.
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.
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.
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());
}
}
[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.
// 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.
// 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?
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);
}
}
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.
// 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());
// 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.
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
}
}
[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.
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);
}
}
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.
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());
}
}
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.
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);
}
}
[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.
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"));
}
}
Priya
Visual Overview
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>
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.
---