Definition
- filter(isActive) keeps only active employees.
- map(toDto) converts to a lightweight DTO.
- groupingBy(dept, TreeMap::new, downstream) groups with sorted department keys.
- collectingAndThen(toList(), sort descending) sorts each group by salary descending.
- Two-level ordering: TreeMap sorts keys, finisher sorts per-group lists.
At National Bank of India, Priya keeps only active staff, converts each to a summary card, files them by department in alphabetical order, and within each department orders the cards highest-paid first for the executive dashboard.
Understanding the Concepts
What does this pipeline produce?
A Map where department keys are alphabetically sorted (TreeMap) and each department's list of DTOs is sorted by salary descending. Active employees only.
How do you implement it?
package com.bank.hr;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
public class FullyOrderedPipeline {
record Employee(String id, String name, String department,
BigDecimal salary, boolean active) {}
record EmployeeDto(String name, String department, BigDecimal salary) {}
static EmployeeDto toDto(Employee e) {
return new EmployeeDto(e.name(), e.department(), e.salary());
}
public static void main(String[] args) {
List<Employee> staff = List.of(
new Employee("SAV2001", "Rajesh", "TRADING", new BigDecimal("1800000"), true),
new Employee("SAV2002", "Priya", "TRADING", new BigDecimal("1200000"), true),
new Employee("SAV2003", "Meera", "TRADING", new BigDecimal("1500000"), false),
new Employee("SAV2004", "Deepak", "RISK", new BigDecimal("1500000"), true),
new Employee("SAV2005", "Amit", "RISK", new BigDecimal("900000"), true),
new Employee("SAV2006", "Kavita", "OPERATIONS", new BigDecimal("1100000"), true)
);
TreeMap<String, List<EmployeeDto>> result = staff.stream()
.filter(Employee::active)
.map(FullyOrderedPipeline::toDto)
.collect(Collectors.groupingBy(
EmployeeDto::department,
TreeMap::new,
Collectors.collectingAndThen(
Collectors.toList(),
list -> {
list.sort(Comparator.comparing(EmployeeDto::salary).reversed());
return list;
})));
result.forEach((dept, dtos) -> {
System.out.println(dept + ":");
dtos.forEach(d ->
System.out.printf(" %s: Rs.%s%n", d.name(), d.salary()));
});
}
}
OPERATIONS:
Kavita: Rs.1100000
RISK:
Deepak: Rs.1500000
Amit: Rs.900000
TRADING:
Rajesh: Rs.1800000
Priya: Rs.1200000
Meera is excluded (inactive). Departments are alphabetical. Each group is salary-descending.
How do the two sorts work at two levels?
| Level | Mechanism | Effect |
|---|---|---|
| Department keys | TreeMap::new supplier | Alphabetical department order |
| Per-department list | collectingAndThen finisher | Salary descending within each group |
Why map to a DTO early?
Mapping to a DTO early in the pipeline keeps the result lightweight and decoupled from the entity. The dashboard does not need all entity fields, and the DTO prevents leaking the domain object to the presentation layer.
What are common mistakes?
Mistake: Using HashMap instead of TreeMap and losing department ordering.
// WRONG: HashMap, no guaranteed department order
Map<String, List<EmployeeDto>> unordered = staff.stream()
.filter(Employee::active)
.map(FullyOrderedPipeline::toDto)
.collect(Collectors.groupingBy(EmployeeDto::department));
// CORRECT: TreeMap for sorted department keys
TreeMap<String, List<EmployeeDto>> ordered = staff.stream()
.filter(Employee::active)
.map(FullyOrderedPipeline::toDto)
.collect(Collectors.groupingBy(
EmployeeDto::department, TreeMap::new, Collectors.toList()));
What about LinkedHashMap for insertion order?
If you want departments in the order they first appear rather than alphabetically, use LinkedHashMap::new instead of TreeMap::new.
What should a senior engineer know?
Combining a sorted key map with a sorted downstream collector produces a fully ordered result in a single collect, rather than grouping first and sorting afterwards in separate steps. This is the archetypal reporting pipeline: filter, transform, group, sort at two levels.
What are common interview follow-up questions?
- "How do you sort departments alphabetically?" Use
TreeMap::newas the map supplier. - "How do you sort each group by salary ascending?" Remove
.reversed()from the comparator. - "Why map to a DTO?" To decouple from the entity and reduce data sent to the presentation layer.
Visual Overview
Input: [Rajesh(active), Priya(active), Meera(inactive),
Deepak(active), Amit(active), Kavita(active)]
│
│ filter(active) → 5 employees
│ map(toDto)
▼
[RajeshDto, PriyaDto, DeepakDto, AmitDto, KavitaDto]
│
│ groupingBy(dept, TreeMap::new)
│ collectingAndThen(toList, sortDesc)
▼
TreeMap:
┌─────────────────────────────────────┐
│ OPERATIONS → [Kavita 1.1M] │
│ RISK → [Deepak 1.5M, │
│ Amit 0.9M] │
│ TRADING → [Rajesh 1.8M, │
│ Priya 1.2M] │
└─────────────────────────────────────┘
Keys: alphabetical | Values: salary desc
Key Takeaway
This is the archetypal multi-level reporting pipeline. Filter active employees, map to a lightweight DTO, group by department with a TreeMap for alphabetical keys, and sort each group by salary descending using collectingAndThen. The result is fully ordered at both levels in a single collect.
At the National Bank of India, this backs an executive dashboard showing active staff by department, highest-paid first. Mapping to a DTO early keeps the result lightweight and decoupled from the entity.
The key insight is that combining a sorted key map (TreeMap) with a sorted downstream collector (collectingAndThen with sort) produces a fully ordered result in one pass. This filter-map-group-sort composition is exactly what real reporting views look like.
Remember one sentence: TreeMap sorts the department keys and collectingAndThen sorts each group's list, producing a fully ordered reporting view in a single collect.
---