Back to Stream: map & Transform
Q25Question 25 of 34

Create a Map<String, Employee> of department to highest-paid employee

Key Concepts
  • toMap(dept, e->e, BinaryOperator.maxBy(comparingDouble(salary))) keeps the highest-paid employee per department
  • BinaryOperator.maxBy wraps a Comparator into a merge function that returns the larger element
  • The value mapper e -> e (identity) keeps the whole Employee object as the map value
  • This produces Map<String, Employee> with no Optional wrapper, unlike groupingBy with maxBy
  • Ties go to whichever element the comparator treats as not-less; extend with thenComparing for determinism
  • The alternative groupingBy(dept, maxBy(...)) yields Map<String, Optional<Employee>>

Definition

toMap(dept, e->e, BinaryOperator.maxBy(comparingDouble(salary))) keeps the highest-paid employee per department with no Optional wrapper.
  • toMap(keyMapper, valueMapper, mergeFunction) builds a map; the merge function resolves same-department collisions.
  • BinaryOperator.maxBy(comparator) turns a Comparator into a merge function returning the larger element.
  • The value mapper e -> e (identity) keeps the whole Employee object.
  • The result is Map with no Optional wrapper.
  • Ties are resolved by whichever element the comparator considers not-less.

For each department at the National Bank of India, Priya wants to pin up the photo of the top earner. As she goes through the staff, whenever she meets another person from a department she already has, she keeps the one who earns more.

Understanding the Concepts

How do you find the highest-paid employee per department?

The key mapper Employee::getDepartment groups by department. The value mapper e -> e keeps the whole Employee object. Because many employees share a department, duplicates are expected, so you supply BinaryOperator.maxBy with a salary comparator as the merge function.

JAVA
package com.bank.hr;

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

public class HighestPaidPerDept {
    public static void main(String[] args) {
        List<Employee> employees = List.of(
            new Employee("Rajesh", "Loans",   75000.0),
            new Employee("Priya",  "Loans",   82000.0),
            new Employee("Amit",   "IT",      68000.0),
            new Employee("Kavita", "IT",      91000.0),
            new Employee("Neha",   "Finance", 78000.0)
        );

        Map<String, Employee> topEarners = employees.stream()
            .collect(Collectors.toMap(
                Employee::getDepartment,
                e -> e,
                BinaryOperator.maxBy(
                    Comparator.comparingDouble(
                        Employee::getSalaryAsDouble))));

        topEarners.forEach((dept, emp) ->
            System.out.println(dept + ": " + emp.getName()
                + " (" + emp.getSalaryAsDouble() + ")"));
    }
}
TEXT
Loans: Priya (82000.0)
IT: Kavita (91000.0)
Finance: Neha (78000.0)

How is this different from groupingBy with maxBy?

The groupingBy approach with maxBy produces Map>, which wraps each value in an Optional. The toMap approach produces Map directly.

| Approach | Result type | Optional wrapper? |
|---|---|---|
| toMap + BinaryOperator.maxBy | Map | No |
| groupingBy + Collectors.maxBy | Map> | Yes |

JAVA
// groupingBy + maxBy: produces Optional wrapper
Map<String, Optional<Employee>> withOptional = employees.stream()
    .collect(Collectors.groupingBy(
        Employee::getDepartment,
        Collectors.maxBy(Comparator.comparingDouble(
            Employee::getSalaryAsDouble))));

// toMap + BinaryOperator.maxBy: no Optional
Map<String, Employee> withoutOptional = employees.stream()
    .collect(Collectors.toMap(
        Employee::getDepartment,
        e -> e,
        BinaryOperator.maxBy(Comparator.comparingDouble(
            Employee::getSalaryAsDouble))));

The toMap form is often more convenient because consumers do not need to unwrap an Optional for each department.

What is BinaryOperator.maxBy?

BinaryOperator.maxBy(comparator) is a convenience factory that turns any Comparator into a BinaryOperator returning the larger of two elements. This avoids writing a verbose ternary lambda.

JAVA
// Verbose lambda
BinaryOperator<Employee> verbose =
    (a, b) -> a.getSalaryAsDouble() >= b.getSalaryAsDouble() ? a : b;

// Clean with maxBy
BinaryOperator<Employee> clean =
    BinaryOperator.maxBy(
        Comparator.comparingDouble(Employee::getSalaryAsDouble));

How do you handle ties?

If two employees in the same department have the same salary, maxBy returns whichever element the comparator considers not-less (the first in encounter order for equal elements). For deterministic tie-breaking, extend the comparator with a secondary key.

JAVA
BinaryOperator<Employee> deterministicMax =
    BinaryOperator.maxBy(
        Comparator.comparingDouble(Employee::getSalaryAsDouble)
            .thenComparing(Employee::getName));

What are common mistakes?

Mistake: using Function.identity() but getting a compile error.

JAVA
// This might cause type inference issues:
Collectors.toMap(
    Employee::getDepartment,
    Function.identity(),  // sometimes the compiler cannot infer
    BinaryOperator.maxBy(comparator));

// Safer: explicit lambda
Collectors.toMap(
    Employee::getDepartment,
    e -> e,  // always works
    BinaryOperator.maxBy(comparator));

When should you use toMap vs groupingBy?

Use toMap when you want exactly one value per key (the top earner, the latest record, the maximum). Use groupingBy when you want a collection per key (all employees per department).

What should a senior engineer know?

  • BinaryOperator.maxBy also has a companion minBy for finding the minimum.
  • The toMap form assumes at least one element per group since there is no empty-key scenario. If a department might legitimately have zero employees after filtering, groupingBy with maxBy and its Optional is safer.
  • For sorted output, supply a TreeMap in the four-argument toMap.

What are common interview follow-up questions?

  • "How do you find the lowest-paid employee per department?" Replace maxBy with BinaryOperator.minBy.
  • "How do you get the top 3 per department?" Use groupingBy with a collectingAndThen that sorts and limits.
  • "How do you find the highest-paid across the whole company?" Use stream().max(comparing(salary)).

Visual Overview

DIAGRAM
  Stream<Employee>
  [Rajesh:Loans:75K, Priya:Loans:82K, Amit:IT:68K, Kavita:IT:91K, Neha:Finance:78K]
       │
       │  toMap(dept, e->e, maxBy(salary))
       ▼
  Processing:
    Rajesh  → Loans:Rajesh(75K)      (insert)
    Priya   → Loans:Priya(82K)       (merge: max(75K,82K) → Priya)
    Amit    → IT:Amit(68K)           (insert)
    Kavita  → IT:Kavita(91K)         (merge: max(68K,91K) → Kavita)
    Neha    → Finance:Neha(78K)      (insert)
       │
       ▼
  Map<String, Employee>
  ┌──────────────────────────────┐
  │  Loans   → Priya (82000.0)  │
  │  IT      → Kavita (91000.0) │
  │  Finance → Neha (78000.0)   │
  └──────────────────────────────┘

Key Takeaway

To find the highest-paid employee per department, collect with toMap using the department as the key, the whole employee as the value, and BinaryOperator.maxBy as the merge function. When two employees collide on the same department, maxBy returns the one with the higher salary.

This form produces a plain Map with no Optional wrapper, which is more convenient than the groupingBy alternative that yields Map>. At the National Bank of India, the recognition dashboard used this to display the top earner per department without unwrapping Optionals.

BinaryOperator.maxBy is a convenience adapter that turns any Comparator into a merge function. For deterministic tie-breaking, extend the comparator with a secondary key like employee name.

Remember one sentence: toMap with BinaryOperator.maxBy gives one highest-paid employee per department with no Optional wrapper.

---

Share
Use ← → arrow keys to navigate