Definition
averagingDouble(mapper)computes the arithmetic mean of the double values extracted by the mapper.- Compensated summation: internally uses a compensated algorithm to reduce floating-point error.
- Returns
Double: the boxed average, suitable for display but not for exact monetary calculations. - Exact money average: sum as BigDecimal and divide with
RoundingModefor audited figures. - Empty group: returns 0.0, though groupingBy only creates groups when elements exist.
Priya wants to know the average pay in each department. For every department she adds up the salaries and divides by how many people there are. The averaging downstream does both automatically.
Understanding the Concepts
How do you compute average salary per department?
package com.bank.hr;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
public class AvgSalaryByDept {
public static void main(String[] args) {
List<Employee> staff = List.of(
new Employee("Rajesh", "Loans", new BigDecimal("95000")),
new Employee("Priya", "IT", new BigDecimal("120000")),
new Employee("Amit", "Finance", new BigDecimal("85000")),
new Employee("Kavita", "Loans", new BigDecimal("78000")),
new Employee("Suresh", "IT", new BigDecimal("110000")));
Map<String, Double> avgByDept = staff.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.averagingDouble(
e -> e.getSalary().doubleValue())));
avgByDept.forEach((dept, avg) ->
System.out.printf("%s: Rs. %.2f%n", dept, avg));
}
}
Loans: Rs. 86500.00
IT: Rs. 115000.00
Finance: Rs. 85000.00
When is Double acceptable vs when do you need BigDecimal?
| Use case | Precision needed | Approach |
|---|---|---|
| Dashboard display | Approximate | averagingDouble (Double) |
| Audited financial report | Exact | BigDecimal sum / count with RoundingMode |
How do you compute an exact BigDecimal average?
import java.math.RoundingMode;
Map<String, BigDecimal> exactAvg = staff.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.collectingAndThen(
Collectors.toList(),
list -> {
BigDecimal sum = list.stream()
.map(Employee::getSalary)
.reduce(BigDecimal.ZERO, BigDecimal::add);
return sum.divide(
BigDecimal.valueOf(list.size()), 2, RoundingMode.HALF_UP);
})));
exactAvg.forEach((dept, avg) ->
System.out.printf("%s: Rs. %s%n", dept, avg.toPlainString()));
What other numeric downstreams are available?
| Downstream | Result | Use case |
|---|---|---|
| counting() | Long | Head count |
| summingDouble(mapper) | Double | Total per group |
| averagingDouble(mapper) | Double | Mean per group |
| summarizingInt(mapper) | IntSummaryStatistics | Min, max, avg, sum, count |
| maxBy(comparator) | Optional
| minBy(comparator) | Optional
What are common mistakes?
Mistake: assuming the double average is exact for money.
// The double average may carry tiny representation error
// For exact monetary calculations, use BigDecimal division
What about sorted department keys?
Map<String, Double> sorted = staff.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
TreeMap::new,
Collectors.averagingDouble(e -> e.getSalary().doubleValue())));
What about summarizingInt for richer statistics?
Map<String, IntSummaryStatistics> stats = staff.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.summarizingInt(Employee::getAge)));
stats.forEach((dept, s) -> System.out.printf(
"%s: count=%d, avg=%.1f, min=%d, max=%d%n",
dept, s.getCount(), s.getAverage(), s.getMin(), s.getMax()));
What should a senior engineer know?
- averagingDouble uses compensated summation internally for better accuracy.
- For audited monetary figures, always use BigDecimal division with RoundingMode.
- groupingBy with a downstream is a one-pass operation.
- The pattern extends to any numeric downstream.
What are common interview follow-up questions?
- "Is the double average exact for money?" No, use BigDecimal for exact figures.
- "How do you get both sum and count?" Use summarizingDouble or summarizingInt.
- "What about departments with one employee?" The average is just that employee's salary.
Visual Overview
groupingBy(dept, averagingDouble(salary)) Loans: (95000 + 78000) / 2 = 86500.0 IT: (120000 + 110000) / 2 = 115000.0 Finance: (85000) / 1 = 85000.0 Map<String, Double> ┌──────────────────────────┐ │ Loans → 86500.0 │ │ IT → 115000.0 │ │ Finance → 85000.0 │ └──────────────────────────┘
Key Takeaway
To compute average salary per department I use groupingBy with averagingDouble as the downstream. It keeps a running sum and count per group and divides at the end, using compensated summation for better accuracy.
For dashboard display the double average is fine, but for audited financial reports I always sum as BigDecimal and divide with an explicit RoundingMode, because double carries tiny representation error.
In my banking project at the National Bank of India this drove a compensation-benchmarking view comparing average pay across departments. The pattern extends to counting, summingDouble, summarizingInt, maxBy, and minBy.
Remember one sentence: groupingBy(dept, averagingDouble(salary)) computes the mean salary per department in one pass, but for exact money use BigDecimal division.
---