Definition
- flatMap per nesting level flattens companies -> departments -> employees into one stream.
- filter(salary > 60000) keeps high earners.
- groupingBy(city) with mapping(name) + collectingAndThen(toList, sort) produces sorted name lists per city.
- Result:
Mapof city to alphabetically sorted employee names.> - Guard nulls at each nesting level.
At National Bank of India group level, Priya opens every subsidiary company, then every department, pulls out all employees into one big pile, keeps the high earners, sorts them into city bins, and within each bin lists names in alphabetical order.
Understanding the Concepts
What makes this pipeline complex?
The data is nested three levels deep: companies contain departments which contain employees. Flattening this hierarchy, filtering, grouping, and sorting within groups requires composing multiple stream operations in a single pipeline.
How do you implement it?
package com.bank.group;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
public class ComplexPipeline {
record Employee(String name, String city, BigDecimal salary) {}
record Department(String name, List<Employee> employees) {}
record Company(String name, List<Department> departments) {}
public static void main(String[] args) {
List<Company> companies = List.of(
new Company("NBI Securities", List.of(
new Department("TRADING", List.of(
new Employee("Rajesh", "Mumbai", new BigDecimal("80000")),
new Employee("Priya", "Delhi", new BigDecimal("70000")))),
new Department("RISK", List.of(
new Employee("Amit", "Mumbai", new BigDecimal("50000")),
new Employee("Kavita", "Chennai",new BigDecimal("65000")))))),
new Company("NBI Insurance", List.of(
new Department("CLAIMS", List.of(
new Employee("Meera", "Mumbai", new BigDecimal("75000")),
new Employee("Deepak", "Delhi", new BigDecimal("55000"))))))
);
Map<String, List<String>> cityToNames = companies.stream()
.flatMap(c -> c.departments().stream()) // Company -> Department
.flatMap(d -> d.employees().stream()) // Department -> Employee
.filter(e -> e.salary().compareTo(new BigDecimal("60000")) > 0)
.collect(Collectors.groupingBy(
Employee::city,
Collectors.collectingAndThen(
Collectors.mapping(Employee::name, Collectors.toList()),
list -> { Collections.sort(list); return list; })));
cityToNames.forEach((city, names) ->
System.out.println(city + ": " + names));
}
}
Mumbai: [Meera, Rajesh]
Delhi: [Priya]
Chennai: [Kavita]
Amit (Rs.50,000) and Deepak (Rs.55,000) are filtered out because they earn <= 60,000.
Why is flatMap the key operation?
Each level of nesting requires one flatMap. The first flatMap turns each company into its departments, and the second turns each department into its employees, flattening the entire hierarchy into a single Stream.
How does the downstream collector work?
- The downstream combines three collectors:
mapping(Employee::name, toList())extracts names into a list.collectingAndThen(collector, finisher)sorts the list after collection.
The result is a sorted list of names per city.
What are common mistakes?
Mistake: Forgetting a flatMap level and getting a stream of departments instead of employees.
// WRONG: only one flatMap, gets Stream<Department>
companies.stream()
.flatMap(c -> c.departments().stream()) // Stream<Department>
.filter(...) // cannot filter Department by salary
// CORRECT: two flatMaps for two nesting levels
companies.stream()
.flatMap(c -> c.departments().stream()) // Stream<Department>
.flatMap(d -> d.employees().stream()) // Stream<Employee>
.filter(e -> e.salary().compareTo(new BigDecimal("60000")) > 0)
What about null safety?
Guard nulls at each nesting level.
companies.stream()
.filter(c -> c.departments() != null)
.flatMap(c -> c.departments().stream())
.filter(d -> d.employees() != null)
.flatMap(d -> d.employees().stream())
.filter(e -> e.salary() != null && e.city() != null)
...
What about using TreeSet for uniqueness and sorting?
If you want unique names sorted, replace toList() + sort with toCollection(TreeSet::new).
What should a senior engineer know?
This pipeline demonstrates the full composability of streams: flatMap for hierarchy flattening, filter for selection, and nested collectors for grouped, transformed, sorted results. Each operation has a clear role, and the whole thing reads as a declarative data transformation rather than nested loops.
What are common interview follow-up questions?
- "How many flatMaps for N nesting levels?" N-1 flatMaps.
- "How do you sort by city name?" Use
TreeMap::newas the map supplier ingroupingBy. - "How do you also include the salary?" Map to a record
NameSalaryinstead of just the name string.
Visual Overview
Companies: [NBI Securities, NBI Insurance]
│
│ flatMap(departments)
▼
Departments: [TRADING, RISK, CLAIMS]
│
│ flatMap(employees)
▼
Employees: [Rajesh 80K, Priya 70K, Amit 50K, Kavita 65K, Meera 75K, Deepak 55K]
│
│ filter(salary > 60K)
▼
[Rajesh 80K, Priya 70K, Kavita 65K, Meera 75K]
│
│ groupingBy(city, mapping(name, sorted))
▼
Mumbai: [Meera, Rajesh] (sorted)
Delhi: [Priya]
Chennai: [Kavita]
Key Takeaway
This pipeline demonstrates the full power of streams on a multi-level hierarchy. flatMap flattens the nesting: one flatMap per level turns companies into departments into employees. After filtering for high earners, groupingBy with a mapping and collectingAndThen downstream produces per-city sorted name lists.
At National Bank of India group level, this backs a group-wide report spanning multiple subsidiary companies. The entire transformation from nested hierarchy to grouped, filtered, sorted output happens in a single declarative pipeline.
The key insight is that each level of nesting needs one flatMap, and combining flatMap, filter, and nested collectors produces a readable, composable pipeline that replaces nested loops. Guard nulls at each level, and choose the per-group collector based on what the consumer needs.
Remember one sentence: one flatMap per nesting level flattens the hierarchy, then groupingBy with nested downstream collectors produces grouped, sorted results in a single pipeline.
---