Back to Blog
Java

Java Collectors.averagingInt: Computing Stream Averages

java collectors averagingint: Learn how to use Java's Collectors.averagingInt to compute the arithmetic mean of integer stream values, handle empty streams, and combin...

Java StreamsCollectorsStream APIAggregationJava 8
Diagram showing integer values flowing through a Java stream into an averaging collector that produces a double average result.

java collectors averagingint requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

What Collectors.averagingInt Does

Collectors.averagingInt is a static factory method in java.util.stream.Collectors that returns a Collector computing the arithmetic mean of integer values extracted from stream elements. You pass it a ToIntFunction that maps each element to an int, and the resulting collector produces a Double representing the average.

The method signature is:

public static <T> Collector<T, ?, Double> averagingInt(ToIntFunction<? super T> mapper)

The collector accumulates the count and the sum of the mapped values, then divides the sum by the count. Because the result is a Double, the division follows floating-point semantics even though the inputs are integers. The average of 2, 5, and 3 is therefore 3.3333333333333335, not a truncated integer.

Basic Usage Example

Consider a list of orders where each order has an integer quantity. To compute the average quantity across all orders:

List<Order> orders = List.of( new Order(2), new Order(5), new Order(3) ); double average = orders.stream() .collect(Collectors.averagingInt(Order::quantity));

The Order::quantity method reference is a ToIntFunction<Order> that returns the int field. The collector handles the accumulation internally, so you do not need to track a running sum or count yourself. The result is 3.3333333333333335.

If the mapped value can be null, the ToIntFunction will throw a NullPointerException when it attempts to unbox the null reference. Make sure the function handles nullable fields explicitly, for example by mapping null to 0 before averaging.

How Empty Streams Behave

A common surprise is the return value for an empty stream. Collectors.averagingInt returns 0.0 when no elements are present, not NaN and not an empty Optional. This differs from mapToInt().average(), which returns an OptionalDouble that is empty for an empty stream.

double average = Stream.<Order>empty() .collect(Collectors.averagingInt(Order::quantity)); // average == 0.0

If your downstream logic needs to distinguish "no data" from "average is zero," check whether the stream is empty before collecting, or use mapToInt().average() and handle the OptionalDouble explicitly. The silent 0.0 result can hide missing data in reports if you do not account for it.

Comparing with mapToInt().average()

The Stream API offers a second way to compute an integer average:

OptionalDouble average = orders.stream() .mapToInt(Order::quantity) .average();

The two approaches differ in return type and in how they fit into a larger collection pipeline. Collectors.averagingInt is a Collector, so it composes with groupingBy, partitioningBy, and teeing. mapToInt().average() is a terminal operation that returns an OptionalDouble.

AspectCollectors.averagingIntmapToInt().average()
Return typeDoubleOptionalDouble
Empty stream result0.0OptionalDouble.empty()
Composes with groupingByYesNo
Intermediate boxingNoneNone

Use Collectors.averagingInt when the average is one of several downstream aggregations. Use mapToInt().average() when you only need the average and want to preserve the empty-stream signal.

Computing Averages Per Group

The collector's real value appears when combined with groupingBy. To compute the average quantity per customer:

Map<String, Double> averageByCustomer = orders.stream() .collect(Collectors.groupingBy( Order::customer, Collectors.averagingInt(Order::quantity) ));

The downstream collector receives each group's elements and produces a Double for that group. This avoids writing a separate loop per customer and keeps the aggregation logic declarative. The same pattern works with partitioningBy when you only need two groups based on a predicate.

Runtime Behavior and Memory

The collector runs in a single pass over the stream and uses a constant amount of memory regardless of stream size. Internally it maintains a mutable accumulator holding a count and a long sum. The sum is stored as a long to avoid overflow when many large int values are accumulated.

For very large streams, the long sum can still overflow if the total exceeds Long.MAX_VALUE, which would require more than two billion elements at the maximum int value. In practice this is rarely a concern, but the accumulator is not arbitrary-precision. Parallel streams use the same collector with concurrent accumulation, and the merge function combines partial sums correctly.

Related Collectors and Selection Criteria

Collectors provides averagingLong and averagingDouble for other numeric types. Choose averagingInt when the mapped value is naturally an int and you want the mean as a double. If the source values are long or double, use the corresponding collector to avoid unnecessary conversion and precision loss.

When you need more than the average, such as count, sum, min, and max in one pass, summarizingInt returns an IntSummaryStatistics object containing all of those values. That is a better fit when the average is one of several statistics you report. For a single average over a small in-memory collection, a simple loop with a running sum and count is equally correct and may be more readable in code that does not otherwise use streams.

java collectors averagingint: Practical Usage and Code Examp | RYUSLOG DEV