Find Average Salary by Department Using Streams in Java
Problem
Grouping with a downstream collector computes one summary value per group, like SQL's GROUP BY with AVG.
Given employees with a department and a salary, print the average salary of each department in alphabetical order.
Java Program
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
public class AverageSalaryByDepartment {
static class Employee {
private final String dept;
private final int salary;
Employee(String dept, int salary) {
this.dept = dept;
this.salary = salary;
}
String getDept() { return dept; }
int getSalary() { return salary; }
}
public static void main(String[] args) {
List<Employee> staff = Arrays.asList(
new Employee("Engineering", 85000),
new Employee("Sales", 62000),
new Employee("Engineering", 95000),
new Employee("HR", 58000),
new Employee("Sales", 70000));
Map<String, Double> averages = staff.stream()
.collect(Collectors.groupingBy(
Employee::getDept,
TreeMap::new, // departments in alphabetical order
Collectors.averagingInt(Employee::getSalary))); // one average per department
averages.forEach((dept, avg) -> System.out.printf("%-12s %.1f%n", dept, avg));
}
}Output
Core Logic
One collector does all the work: group by department, and inside each group average the salaries.
- 1
Employee::getDeptis the grouping key. - 2
TreeMap::newkeeps departments in alphabetical order. - 3
Collectors.averagingInt(Employee::getSalary)averages each group and returns aDouble. - 4
printf("%-12s %.1f%n")prints the department padded to 12 characters and the average with one decimal.
Key Point: averagingInt always returns a Double, even when every salary is a whole number.
Why: Each employee is added once to a TreeMap with d departments, which stores one running average per department.
Key Concepts
Approach 2: Count, Total and Max per Department
import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
public class SalaryStatsByDepartment {
static class Employee {
private final String dept;
private final int salary;
Employee(String dept, int salary) {
this.dept = dept;
this.salary = salary;
}
String getDept() { return dept; }
int getSalary() { return salary; }
}
public static void main(String[] args) {
List<Employee> staff = Arrays.asList(
new Employee("Engineering", 85000),
new Employee("Sales", 62000),
new Employee("Engineering", 95000),
new Employee("HR", 58000),
new Employee("Sales", 70000));
Map<String, IntSummaryStatistics> stats = staff.stream()
.collect(Collectors.groupingBy(Employee::getDept, TreeMap::new,
Collectors.summarizingInt(Employee::getSalary))); // count, sum, min, max, average at once
stats.forEach((dept, s) -> System.out.println(dept + ": count=" + s.getCount()
+ ", total=" + s.getSum() + ", max=" + s.getMax()));
}
}
Output
Core Logic
summarizingInt collects count, sum, minimum, maximum and average for each group in a single pass.
- 1The grouping is the same, but the downstream collector is
summarizingInt. - 2Each department maps to an
IntSummaryStatisticsobject. - 3The loop prints the count, total and maximum for each department.
Key Point: Use one summarizingInt instead of separate counting, summingInt and maxBy passes when you need several numbers.
Why: One pass updates a statistics object per department.
Key Concepts
Interview Questions
How do you find the department with the highest average salary?
Stream the result map's entries and take max(Map.Entry.comparingByValue()), then read its key.
How would you round the averages to two decimals?
Format them when printing with %.2f, or wrap the value in BigDecimal and call setScale(2, RoundingMode.HALF_UP).