Back to Stream Practice: Employee Analysis
Q1Question 1 of 43

Group employees by field (city/department) + filter by city/age using streams

Key Concepts
  • groupingBy(Employee::getCity) creates Map<String, List<Employee>> grouping employees by city.
  • groupingBy(classifier, counting()) replaces the list with a count per group.
  • filter(e -> "Mumbai".equals(e.getCity())) keeps only Mumbai employees before grouping.
  • Combining filter and groupBy: filter Mumbai employees first, then group by department.
  • Custom classifier: groupingBy(e -> (e.getAge() / 10) * 10) groups by age decade.
  • mapping(Employee::getName, toList()) as downstream produces names instead of full objects per group.
  • These patterns replace manual HashMap accumulation loops with single declarative collect calls.

Definition

groupingBy(Employee::getCity) produces Map<City, List<Employee>>; combine with filter for conditional grouping and downstream collectors for counts, names, or nested grouping.
DIAGRAM
- **Group by field**: `groupingBy(Employee::getCity)` creates `Map<String, List<Employee>>`.
- **Count per group**: `groupingBy(Employee::getDepartment, counting())` gives `Map<String, Long>`.
- **Filter then group**: `filter(e -> "Mumbai".equals(e.getCity()))` then `groupingBy(Employee::getDepartment)`.
- **Custom classifier**: `groupingBy(e -> (e.getAge() / 10) * 10)` groups by age decade.
- **Mapping downstream**: `groupingBy(dept, mapping(Employee::getName, toList()))` produces names per department.

Grouping is the bank sorting all employees into department-labelled folders. Each folder has a list of employees. Filtering picks employees from Mumbai only or employees above age 50. Combined: give me Mumbai employees grouped by department is filter first, then group.

Understanding the Concepts

How do you group and filter employees with streams?

JAVA
package com.bank.service;

import java.util.*;
import java.util.stream.Collectors;

public class EmployeeGrouping {
    record Employee(String name, String department, String city, int age, double salary) {}

    public static void main(String[] args) {
        List<Employee> employees = List.of(
            new Employee("Rajesh", "IT", "Mumbai", 35, 85000),
            new Employee("Priya", "IT", "Delhi", 28, 72000),
            new Employee("Amit", "Finance", "Mumbai", 42, 95000),
            new Employee("Deepa", "Finance", "Chennai", 55, 110000),
            new Employee("Vikram", "IT", "Mumbai", 31, 78000)
        );

        // Group by city
        Map<String, List<Employee>> byCity = employees.stream()
            .collect(Collectors.groupingBy(Employee::city));
        System.out.println("Cities: " + byCity.keySet());

        // Count by department
        Map<String, Long> countByDept = employees.stream()
            .collect(Collectors.groupingBy(Employee::department, Collectors.counting()));
        System.out.println("Dept count: " + countByDept);
    }
}
TEXT
Cities: [Mumbai, Delhi, Chennai]
Dept count: {IT=3, Finance=2}

How do you filter then group?

JAVA
// Filter Mumbai, then group by department
Map<String, List<Employee>> mumbaiByDept = employees.stream()
    .filter(e -> "Mumbai".equals(e.city()))
    .collect(Collectors.groupingBy(Employee::department));
System.out.println("Mumbai depts: " + mumbaiByDept.keySet());
// {IT=[Rajesh, Vikram], Finance=[Amit]}

Filter first, then group. This avoids grouping all employees and then filtering each group.

How do you filter by age?

JAVA
// Filter age > 40
List<Employee> seniors = employees.stream()
    .filter(e -> e.age() > 40)
    .toList();
System.out.println("Seniors: " + seniors.stream().map(Employee::name).toList());
// [Amit, Deepa]

How do you group by age decade?

JAVA
// Group by age decade
Map<Integer, List<Employee>> byDecade = employees.stream()
    .collect(Collectors.groupingBy(e -> (e.age() / 10) * 10));
System.out.println("Decades: " + byDecade.keySet());
// [30, 20, 40, 50]

How do you extract names per department?

JAVA
// Names per department (mapping downstream)
Map<String, List<String>> namesByDept = employees.stream()
    .collect(Collectors.groupingBy(Employee::department,
        Collectors.mapping(Employee::name, Collectors.toList())));
System.out.println("IT names: " + namesByDept.get("IT"));
// [Rajesh, Priya, Vikram]

How do you get sorted keys with TreeMap?

JAVA
// Sorted keys: TreeMap factory
Map<String, Long> sorted = employees.stream()
    .collect(Collectors.groupingBy(Employee::city,
        TreeMap::new, Collectors.counting()));
System.out.println("Sorted: " + sorted);
// {Chennai=1, Delhi=1, Mumbai=3}

What are common mistakes?

JAVA
// WRONG: grouping all employees first, then filtering each group
Map<String, List<Employee>> all = employees.stream()
    .collect(Collectors.groupingBy(Employee::city));
List<Employee> mumbai = all.get("Mumbai"); // two-step, inefficient

// CORRECT: filter first, then group
Map<String, List<Employee>> mumbaiByDept = employees.stream()
    .filter(e -> "Mumbai".equals(e.city()))
    .collect(Collectors.groupingBy(Employee::department));

What should a senior engineer know?

  • Each grouping replaces a manual HashMap loop. The filter-then-group pattern avoids grouping all employees and then filtering each group.
  • The mapping downstream produces lightweight results (names instead of full objects).
  • TreeMap::new as the map factory produces alphabetically sorted keys.
  • For multi-level grouping, nest groupingBy inside groupingBy.
  • In production, push filter+group to database GROUP BY for large datasets.

Visual Overview

DIAGRAM
  Employee Stream Patterns:
  ─────────────────────────
  Group by city:    groupingBy(Employee::city)         → Map<City, List<Emp>>
  Count by dept:    groupingBy(dept, counting())       → Map<Dept, Long>
  Filter + group:   filter(Mumbai).groupingBy(dept)    → Map<Dept, List<Emp>>
  Names per dept:   groupingBy(dept, mapping(name))    → Map<Dept, List<String>>
  Age decade:       groupingBy(age/10*10)              → Map<Int, List<Emp>>

Key Takeaway

Grouping uses groupingBy with a classifier function. Filtering uses filter with a predicate. The powerful combination is filter then group: filter Mumbai employees first, then group by department. Each replaces a manual HashMap loop with a single declarative collect call.

Downstream collectors customise the grouped values: counting for headcount, mapping for extracting specific fields, and nested groupingBy for multi-level grouping.

Remember one sentence: filter first to narrow the dataset, then groupingBy to partition, with downstream collectors to customise each group.

---

Share
Use ← → arrow keys to navigate