reduce()
Fold a stream into a single value with an identity and an associative function, and know when sum(), max() or collect() is the better tool.
T reduce(T identity, BinaryOperator<T> accumulator)
Optional<T> reduce(BinaryOperator<T> accumulator)
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)- Added in Java Java 8
Parameters
| Parameter | Type | Description |
|---|---|---|
$identityoptional | T or U | A neutral starting value: combining it with any element must give that element back (0 for addition, 1 for multiplication, an empty string for concatenation). |
$accumulator | BinaryOperator<T> or BiFunction<U, ? super T, U> | An associative, stateless function that folds one more element into the running result. |
$combineroptional | BinaryOperator<U> | Three-argument form only: merges two partial results, used when a parallel stream is split. Must be compatible with the accumulator. |
Return value
T, Optional<T> or UThe result of combining all elements. The identity form returns the identity for an empty stream; the no-identity form returns Optional.empty() for an empty stream.
Errors
NullPointerException from the one-argument form if the result of the reduction is null. Exceptions thrown by the functions surface directly.
reduce combines all elements into one value by applying a function repeatedly: a sum, a product, a maximum, a merged permission set. It has three overloads, and choosing the right one depends on whether the stream can be empty and whether the result type differs from the element type.
The three forms
reduce(identity, accumulator): starts from the identity, always returns a value. An empty stream gives the identity.reduce(accumulator): no starting value, returnsOptional<T>, empty for an empty stream. Use it when there is no sensible neutral value, as with a maximum.reduce(identity, accumulator, combiner): the result typeUdiffers from the element type, for example summing anintfield of each order. The combiner merges partial results in parallel streams.
Rules for the functions
The accumulator must be associative: (a + b) + c must equal a + (b + c). Addition, multiplication, min, max and string concatenation are; subtraction and averaging are not. The identity must be neutral: 0 for addition, 1 for multiplication. A sequential stream with reduce(10, Integer::sum) just adds 10, but a parallel stream adds it once per chunk, so the answer changes with the number of splits. These rules are what make parallel reduction correct.
Money: BigDecimal, not double
Summing prices as double accumulates binary rounding errors. BigDecimal has no primitive stream, so reduce(BigDecimal.ZERO, BigDecimal::add) is the standard way to total amounts. Alternatively, keep money as whole minor units (pence, cents) in a long and use mapToLong(...).sum().
When not to use reduce
- Numbers:
mapToInt(...).sum(),max()andaverage()are clearer and avoid boxing. - Largest by a field:
max(Comparator.comparing(...))says what it means. - Lists, maps, strings:
reducecreates a new value on every step, so building a list with it copies the list each time. Usecollect,toList()orCollectors.joining, which add to a mutable container.
In service code, explicit reduce is mostly found for BigDecimal totals, merging small immutable values such as permission sets, and composing functions or predicates.
Example
When to use it
- Total the BigDecimal line amounts of an invoice before applying tax.
- Combine the permissions of all roles a user holds into one effective set for an authorisation check.
- Compose a list of small validation predicates into one with reduce(x -> true, Predicate::and).
Frequently asked questions
How do I sum BigDecimal values with a Java stream?
Use reduce with zero as the identity: list.stream().reduce(BigDecimal.ZERO, BigDecimal::add). If the values come from objects, map first: items.stream().map(Item::price).reduce(BigDecimal.ZERO, BigDecimal::add).
Why does reduce return an Optional sometimes?
The one-argument reduce(accumulator) has no starting value, so for an empty stream there is nothing to return and it gives Optional.empty(). The two- and three-argument forms take an identity and return it for an empty stream.
Should I use reduce or collect?
Use reduce for combining immutable values such as numbers, BigDecimal or small sets. Use collect for building mutable containers such as lists, maps and StringBuilders, because reduce would create a new container for every element.
More examples
Totalling money with BigDecimal
BigDecimal addition is exact, so the total keeps two decimal places. The same idea with double shows the binary rounding error that makes floating point unsuitable for money.
void main() {
List<BigDecimal> prices = List.of(
new BigDecimal("19.99"),
new BigDecimal("5.01"),
new BigDecimal("0.10"));
BigDecimal total = prices.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
IO.println(total);
double asDouble = Stream.of(0.1, 0.2).reduce(0.0, Double::sum);
IO.println(asDouble);
}
// Output:
// 25.10
// 0.30000000000000004Empty streams and the three return styles
With an identity, an empty stream gives the identity; without one, you get an empty Optional and must decide what to do. For multiplication the neutral identity is 1, not 0.
void main() {
List<Integer> none = List.of();
IO.println(none.stream().reduce(0, Integer::sum));
IO.println(none.stream().reduce(Integer::sum));
IO.println(none.stream().mapToInt(Integer::intValue).sum());
IO.println(List.of(3, 4).stream().reduce(1, (a, b) -> a * b));
}
// Output:
// 0
// Optional.empty
// 0
// 12Merging permission sets
Set union is associative and the empty set is its identity, so this is a valid reduction. It copies a set per step, which is fine for a handful of roles; for large data, flatMap the permissions and collect into a set instead.
record Role(String name, Set<String> permissions) {}
void main() {
List<Role> roles = List.of(
new Role("viewer", Set.of("orders:read")),
new Role("editor", Set.of("orders:read", "orders:write")),
new Role("billing", Set.of("invoices:read")));
Set<String> effective = roles.stream()
.map(Role::permissions)
.reduce(Set.of(), (a, b) -> {
Set<String> merged = new TreeSet<>(a);
merged.addAll(b);
return merged;
});
IO.println(effective);
}
// Output:
// [invoices:read, orders:read, orders:write]
Discussion