Definition
Collectors.groupingBy(classifier)partitions stream elements into a Map keyed by the classifier's return value.- Default downstream:
toList(), so each group is aList. - Default map type:
HashMap, with no guaranteed key order. - Ordered keys: use
groupingBy(classifier, TreeMap::new, toList())for sorted keys. - Null classifier: throws
NullPointerException; map null departments to a placeholder first.
Priya at the National Bank of India has a pile of staff cards and wants to sort them into labelled trays: one tray per department. She looks at each card's department and drops it into the matching tray.
Understanding the Concepts
How does groupingBy work?
Collectors.groupingBy takes a classifier function that maps each element to a key. For each element it computes the key, finds or creates the list for that key in the result map, and adds the element.
How do you group employees by department?
package com.bank.hr;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
public class GroupByDepartment {
public static void main(String[] args) {
List<Employee> staff = List.of(
new Employee("Rajesh", "Loans", new BigDecimal("95000")),
new Employee("Priya", "IT", new BigDecimal("120000")),
new Employee("Amit", "Finance", new BigDecimal("85000")),
new Employee("Kavita", "Loans", new BigDecimal("78000")),
new Employee("Suresh", "IT", new BigDecimal("110000")));
Map<String, List<Employee>> byDept = staff.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
byDept.forEach((dept, emps) -> {
System.out.println(dept + ":");
emps.forEach(e -> System.out.println(" " + e.getName()));
});
}
}
Loans:
Rajesh
Kavita
IT:
Priya
Suresh
Finance:
Amit
How do you get sorted department keys?
The three-argument form accepts a map factory.
Map<String, List<Employee>> sorted = staff.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
TreeMap::new,
Collectors.toList()));
// Keys: Finance, IT, Loans (alphabetical)
What are the three forms of groupingBy?
| Form | Arguments | Default downstream | Default map |
|---|---|---|---|
| One-arg | classifier | toList() | HashMap |
| Two-arg | classifier, downstream | specified | HashMap |
| Three-arg | classifier, mapFactory, downstream | specified | specified |
What are common mistakes?
Mistake: the classifier returns null, throwing NullPointerException.
// WRONG: null department crashes groupingBy
staff.stream()
.collect(Collectors.groupingBy(Employee::getDepartment)); // NPE if dept is null!
// CORRECT: map null to a placeholder first
staff.stream()
.collect(Collectors.groupingBy(
e -> e.getDepartment() != null ? e.getDepartment() : "UNKNOWN"));
What about unmodifiable inner lists?
import java.util.stream.Collectors;
Map<String, List<Employee>> immutable = staff.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.toUnmodifiableList()));
// Each inner list is unmodifiable
What about grouping into Sets instead of Lists?
Map<String, Set<Employee>> byDeptSet = staff.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.toSet()));
What about multi-level grouping?
// Group by department, then by city within each department
Map<String, Map<String, List<Employee>>> nested = staff.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.groupingBy(Employee::getCity)));
What should a senior engineer know?
- groupingBy processes elements in one pass, building the map incrementally.
- The classifier must not return null; HashMap does not support null keys in groupingBy.
- For concurrent collection, use
Collectors.groupingByConcurrentwith a ConcurrentHashMap. - The default inner lists are mutable ArrayLists, which is fine for most uses.
What are common interview follow-up questions?
- "How do you count per group?" Use
groupingBy(dept, counting()). - "How do you get sorted keys?" Use the three-argument form with TreeMap::new.
- "What if the department is null?" Map to a placeholder before grouping.
Visual Overview
List<Employee>
[Rajesh/Loans, Priya/IT, Amit/Finance, Kavita/Loans, Suresh/IT]
│
│ collect(groupingBy(Employee::getDepartment))
▼
Map<String, List<Employee>>
┌──────────────────────────────────────┐
│ Loans → [Rajesh, Kavita] │
│ IT → [Priya, Suresh] │
│ Finance → [Amit] │
└──────────────────────────────────────┘
Key Takeaway
To group employees by department I use Collectors.groupingBy with the department getter as the classifier. In a single pass it builds a Map where each key is a department and each value is the list of employees in that department.
The single-argument form uses toList as the default downstream and returns a HashMap with no guaranteed key order. For alphabetical keys I use the three-argument form with TreeMap::new.
In my banking project at the National Bank of India this built the departmental staff directory. The classifier must never return null (it throws), so I map null departments to a placeholder first, and the default inner lists are mutable ArrayLists.
Remember one sentence: groupingBy(getDepartment) partitions employees into a Map of department to employee list in one pass.
---