Collectors.groupingBy is a powerful collector in Java Streams used to group elements of a stream based on a classifier function. It is commonly used for aggregation tasks like counting, summing, or finding max/min per group.
1. Basic Concept
Collectors.groupingBy groups stream elements based on a classifier function:
Map<K, List<T>> groupedMap = stream.collect(Collectors.groupingBy(classifierFunction));
Kis the type of the grouping key.Tis the type of stream elements.- The result is a
Map<K, List<T>>by default.
2. Example 1: Group Strings by Length
List<String> words = Arrays.asList("Java", "Stream", "API", "Lambda", "Code");
Map<Integer, List<String>> groupedByLength = words.stream()
.collect(Collectors.groupingBy(String::length));
groupedByLength.forEach((length, list) ->
System.out.println(length + ": " + list));
Output:
3: [API] 4: [Java, Code] 6: [Stream, Lambda]
Explanation:
String::lengthis the classifier function.- Strings are grouped into lists based on their length.
3. Example 2: Group Employees by Department
Assume an Employee class:
public class Employee {
private String name;
private String department;
private double salary;
public Employee(String name, String department, double salary) {
this.name = name;
this.department = department;
this.salary = salary;
}
public String getName() { return name; }
public String getDepartment() { return department; }
public double getSalary() { return salary; }
}
And a list of employees:
List<Employee> employees = Arrays.asList(
new Employee("Alice", "HR", 50000),
new Employee("Bob", "IT", 75000),
new Employee("Charlie", "IT", 80000),
new Employee("David", "HR", 60000)
);
Group by Department
Map<String, List<Employee>> employeesByDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
employeesByDept.forEach((dept, list) -> {
System.out.print(dept + ": ");
list.forEach(emp -> System.out.print(emp.getName() + " "));
System.out.println();
});
Output:
HR: Alice David IT: Bob Charlie
Explanation:
Employee::getDepartmentis the classifier function.- Employees are grouped into lists based on their department.
4. Example 3: Group and Count Elements
You can combine groupingBy with downstream collectors like counting():
Map<String, Long> countByDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment, Collectors.counting()));
countByDept.forEach((dept, count) -> System.out.println(dept + ": " + count));
Output:
HR: 2 IT: 2
Explanation:
Collectors.counting()counts the number of employees in each group.- The result is a
Map<String, Long>.
5. Example 4: Group and Find Max Salary Per Department
Map<String, Optional<Employee>> highestPaid = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.maxBy(Comparator.comparing(Employee::getSalary))
));
highestPaid.forEach((dept, empOpt) -> empOpt.ifPresent(emp ->
System.out.println(dept + ": " + emp.getName() + " - " + emp.getSalary())
));
Output:
HR: David - 60000.0 IT: Charlie - 80000.0
Explanation:
Collectors.maxBy()finds the employee with the highest salary in each department.- The result is a
Map<String, Optional<Employee>>.
6. Example 5: Group and Transform the Result
You can transform the downstream result using collectingAndThen:
Map<String, Employee> highestPaid2 = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.collectingAndThen(
Collectors.maxBy(Comparator.comparing(Employee::getSalary)),
Optional::get
)
));
highestPaid2.forEach((dept, emp) ->
System.out.println(dept + ": " + emp.getName() + " - " + emp.getSalary()));
Output:
HR: David - 60000.0 IT: Charlie - 80000.0
Note:
Optional::getthrows an exception if the group is empty. Use only if you are sure groups are non-empty or handle safely.
7. Summary
Collectors.groupingBy(classifier)groups elements by a key.- Can be combined with downstream collectors like
counting(),maxBy(),summarizingDouble(). collectingAndThenallows post-processing, like unwrappingOptionalor transforming the result.- Useful for aggregation tasks, reporting, or statistics per group.
