Back to Stream Practice: Employee Analysis
Q15Question 15 of 43

Create a summary report per department: count, avg, min, max, total salary

Key Concepts
  • groupingBy(dept, summarizingDouble(salary)) gives all five metrics per department in one pass
  • DoubleSummaryStatistics provides getCount, getSum, getMin, getMax, getAverage
  • For formatted output, use String.format or printf with the statistics getters
  • Sort departments by name with TreeMap or by a metric with sorted on the entrySet
  • For exact BigDecimal totals, use a custom collector or post-process with BigDecimal reduce
  • This is the go-to pattern for dashboard summary cards in banking applications

Definition

Use groupingBy(dept, summarizingDouble(salary)) to produce a Map of DoubleSummaryStatistics per department, then format the count, sum, min, max, and average from each statistics object.
  • groupingBy(dept, summarizingDouble(salary)) produces Map.
  • DoubleSummaryStatistics provides getCount(), getSum(), getMin(), getMax(), getAverage().
  • One pass per department collects all five metrics simultaneously.
  • Formatted report: use printf or String.format for aligned output.

The bank CFO asks for a one-page salary report: for each department, show how many employees, total payroll, lowest salary, highest salary, and average salary. One DoubleSummaryStatistics per department provides all five numbers.

Understanding the Concepts

How do you create a per-department summary report?

JAVA
package com.bank.hr;

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

public class DepartmentSummaryReport {
    public static void main(String[] args) {
        List<Employee> employees = Employee.getSampleEmployees();

        Map<String, DoubleSummaryStatistics> deptStats = employees.stream()
            .collect(Collectors.groupingBy(
                Employee::getDepartment,
                Collectors.summarizingDouble(
                    e -> e.getSalary().doubleValue())));

        System.out.printf("%-15s %5s %15s %12s %12s %12s%n",
            "Department", "Count", "Total", "Min", "Max", "Average");
        System.out.println("-".repeat(75));

        new TreeMap<>(deptStats).forEach((dept, s) ->
            System.out.printf("%-15s %5d %,15.0f %,12.0f %,12.0f %,12.0f%n",
                dept, s.getCount(), s.getSum(),
                s.getMin(), s.getMax(), s.getAverage()));
    }
}
TEXT
Department      Count           Total          Min          Max      Average
---------------------------------------------------------------------------
COMPLIANCE          2       2,100,000      800,000    1,300,000    1,050,000
OPERATIONS          2       1,800,000      700,000    1,100,000      900,000
RISK                3       3,600,000      900,000    1,500,000    1,200,000
TRADING             3       4,500,000    1,200,000    1,800,000    1,500,000

What are common mistakes?

Mistake: computing each metric separately with five different streams.

JAVA
// WRONG: five separate passes through the data
Map<String, Long> count = ...groupingBy(dept, counting());
Map<String, Double> sum = ...groupingBy(dept, summingDouble(salary));
Map<String, Double> avg = ...groupingBy(dept, averagingDouble(salary));
// etc. - wasteful!

// CORRECT: one pass with summarizingDouble
Map<String, DoubleSummaryStatistics> all = ...groupingBy(dept, summarizingDouble(salary));

What about adding a grand total row?

JAVA
import java.util.DoubleSummaryStatistics;

DoubleSummaryStatistics grandTotal = employees.stream()
    .collect(Collectors.summarizingDouble(
        e -> e.getSalary().doubleValue()));

System.out.printf("%-15s %5d %,15.0f %,12.0f %,12.0f %,12.0f%n",
    "TOTAL", grandTotal.getCount(), grandTotal.getSum(),
    grandTotal.getMin(), grandTotal.getMax(), grandTotal.getAverage());

What about using exact BigDecimal for totals?

JAVA
Map<String, BigDecimal> exactTotals = employees.stream()
    .collect(Collectors.groupingBy(
        Employee::getDepartment,
        Collectors.reducing(
            BigDecimal.ZERO,
            Employee::getSalary,
            BigDecimal::add)));

DoubleSummaryStatistics has floating-point drift. For audited totals, use BigDecimal reduce per department.

What about exporting as CSV?

JAVA
String csv = new TreeMap<>(deptStats).entrySet().stream()
    .map(e -> String.format("%s,%d,%.0f,%.0f,%.0f,%.0f",
        e.getKey(), e.getValue().getCount(),
        e.getValue().getSum(), e.getValue().getMin(),
        e.getValue().getMax(), e.getValue().getAverage()))
    .collect(Collectors.joining("\n",
        "Department,Count,Total,Min,Max,Average\n", ""));

What about sorting by a specific metric?

JAVA
// Sort departments by total salary descending
deptStats.entrySet().stream()
    .sorted(Comparator.comparingDouble(
        (Map.Entry<String, DoubleSummaryStatistics> e) ->
            e.getValue().getSum()).reversed())
    .forEach(e -> System.out.println(
        e.getKey() + ": Rs." + e.getValue().getSum()));

What are the edge cases?

An empty employee list produces an empty map. A department with one employee has min == max == average == that salary, and count == 1.

What are common interview follow-up questions?

  • "How do you add a grand total row?" Compute a separate DoubleSummaryStatistics for all employees.
  • "How do you export as CSV?" Map the formatted strings to CSV format with joining("\n").

Visual Overview

DIAGRAM
  groupingBy(dept, summarizingDouble(salary))
  ┌──────────────────────────────────────────────────┐
  │  Dept         Count   Total    Min    Max    Avg  │
  │  TRADING      3       4.5M     1.2M   1.8M  1.5M │
  │  RISK         3       3.6M     0.9M   1.5M  1.2M │
  │  OPERATIONS   2       1.8M     0.7M   1.1M  0.9M │
  │  COMPLIANCE   2       2.1M     0.8M   1.3M  1.05M│
  └──────────────────────────────────────────────────┘

Key Takeaway

A department summary report uses groupingBy with summarizingDouble to collect all five salary metrics (count, sum, min, max, average) per department in a single pass. One DoubleSummaryStatistics object per department provides all the numbers needed for a dashboard card.

Remember one sentence: summarizingDouble as a downstream gives count, sum, min, max, and average per group in one pass, replacing five separate aggregation queries.

---

Share
Use ← → arrow keys to navigate