Back to Stream: Grouping & Partitioning
Q10Question 10 of 22

Group employees by their joining year into Map<Integer, List<Employee>>

Key Concepts
  • groupingBy(e -> e.getJoinDate().getYear()) uses the join year as the grouping key
  • LocalDate.getYear() returns the year as an int, producing Map<Integer, List<Employee>>
  • Supply TreeMap::new for chronological year order in the output
  • Combine with counting() as a downstream to get hires per year instead of full lists
  • Guard against null join dates before calling getYear to avoid NullPointerException
  • Deriving the grouping key from a date field keeps the data model clean (no stored year column)

Definition

groupingBy(e -> e.getJoinDate().getYear()) buckets employees by the year they joined.
  • getJoinDate().getYear() extracts the year from each employee's join date as an int.
  • groupingBy collects employees sharing the same year into a list.
  • The result is Map> keyed by year.
  • TreeMap::new gives chronological year order for reports.
  • Guard null join dates before calling getYear().

Priya at the National Bank of India files employees by the year they joined: a 2020 tray, a 2021 tray, and so on, based on the year part of their joining date.

Understanding the Concepts

How do you group by joining year?

Extract the year from the join date and use it as the classifier.

JAVA
package com.bank.hr;

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

public class GroupByJoinYear {
    public static void main(String[] args) {
        List<Employee> employees = List.of(
            new Employee("Rajesh", LocalDate.of(2020, 3, 1)),
            new Employee("Priya",  LocalDate.of(2021, 6, 15)),
            new Employee("Amit",   LocalDate.of(2020, 9, 10)),
            new Employee("Kavita", LocalDate.of(2022, 1, 5)),
            new Employee("Neha",   LocalDate.of(2021, 11, 20))
        );

        Map<Integer, List<Employee>> byYear = employees.stream()
            .collect(Collectors.groupingBy(
                e -> e.getJoinDate().getYear(),
                TreeMap::new,
                Collectors.toList()));

        byYear.forEach((year, emps) ->
            System.out.println(year + ": " + emps.stream()
                .map(Employee::getName)
                .collect(Collectors.joining(", "))));
    }
}
TEXT
2020: Rajesh, Amit
2021: Priya, Neha
2022: Kavita

How do you count hires per year?

JAVA
Map<Integer, Long> hiresPerYear = employees.stream()
    .collect(Collectors.groupingBy(
        e -> e.getJoinDate().getYear(),
        TreeMap::new,
        Collectors.counting()));

hiresPerYear.forEach((year, count) ->
    System.out.println(year + ": " + count + " hires"));
TEXT
2020: 2 hires
2021: 2 hires
2022: 1 hires

How do you guard against null join dates?

JAVA
// WRONG: NullPointerException if joinDate is null
employees.stream()
    .collect(Collectors.groupingBy(
        e -> e.getJoinDate().getYear()));

// CORRECT: filter nulls first
employees.stream()
    .filter(e -> e.getJoinDate() != null)
    .collect(Collectors.groupingBy(
        e -> e.getJoinDate().getYear(),
        TreeMap::new,
        Collectors.toList()));

How do you group by decade instead of year?

JAVA
Map<Integer, List<Employee>> byDecade = employees.stream()
    .collect(Collectors.groupingBy(
        e -> (e.getJoinDate().getYear() / 10) * 10));
// Keys: 2020 (for 2020-2029)

What should a senior engineer know?

  • Deriving the grouping key on the fly from a date field keeps the data model clean; no redundant year column needed.
  • Use TreeMap to get chronological order for cohort analysis reports.
  • groupingBy only creates keys for years that have employees. To include years with zero hires, merge with a pre-built map of all expected years.
  • This pattern generalises to grouping by month, quarter, or any temporal unit.

What are common interview follow-up questions?

DIAGRAM
- "How do you group by month instead of year?" Use e -> e.getJoinDate().getMonth() as the classifier.
- "How do you group by year-month?" Use e -> YearMonth.from(e.getJoinDate()).
- "How do you find the year with the most hires?" Stream the hires-per-year map and take max by value.

Visual Overview

DIAGRAM
  Stream<Employee>
  [Rajesh:2020-03, Priya:2021-06, Amit:2020-09, Kavita:2022-01, Neha:2021-11]
       │
       │  classifier: getJoinDate().getYear()
       ▼
  Mapping:
    Rajesh → 2020
    Priya  → 2021
    Amit   → 2020
    Kavita → 2022
    Neha   → 2021
       │
       │  groupingBy (TreeMap for order)
       ▼
  Map<Integer, List<Employee>>
  ┌────────────────────────────┐
  │  2020 → [Rajesh, Amit]    │
  │  2021 → [Priya, Neha]     │
  │  2022 → [Kavita]          │
  └────────────────────────────┘

Key Takeaway

To group employees by joining year, pass a classifier that extracts the year from each join date. LocalDate.getYear() returns the year as an int, and groupingBy collects employees sharing a year into a list, producing a cohort view for HR analysis.

Supply a TreeMap for chronological order and combine with counting() for hires-per-year. At the National Bank of India, this drove the annual hiring report and cohort retention analysis.

Guard against null join dates before calling getYear, and derive the key on the fly to keep the data model clean. This pattern generalises to grouping by month, quarter, decade, or any temporal unit.

Remember one sentence: groupingBy(e -> getJoinDate().getYear()) groups employees into year cohorts, and TreeMap::new ensures chronological order.

---

Share
Use ← → arrow keys to navigate