Back to Blog
Java

Java Collectors.summingInt: Summing Integer Streams

java collectors summingint: Learn how to use Java Collectors.summingInt to sum integer values from streams, including custom object properties, with practical examples...

Java StreamsCollectorsSummingIntFunctional ProgrammingPrimitive Streams
Java Collectors.summingInt concept illustration showing a stream of integers being summed into a single value.

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

When you need to sum a series of int values from a Java stream, Collectors.summingInt is the collector designed for that task. It returns a Collector that reduces the stream to a single Integer sum, and it fits naturally into the Stream.collect pipeline. The method signature is public static <T> Collector<T, ?, Integer> summingInt(ToIntFunction<? super T> mapper), meaning it accepts a function that extracts an int from each stream element. This makes it useful for summing a numeric property from a collection of objects, not just raw integers.

How Collectors.summingInt Works

The collector internally uses a mutable accumulator to track the running total. For each element in the stream, the mapper is applied to produce an int, which is added to the accumulator. Because the accumulator is a primitive int array, there is no boxing per element during the reduction. Only the final result is boxed into an Integer. This design keeps the overhead low compared to approaches that box every intermediate value.

The collector is stateful and not thread-safe, but that is not a concern because Stream.collect guarantees that the collector is only used sequentially within a single thread. If you need parallel reduction, the collector is designed to be concurrent-safe by combining partial results via its combiner, which simply adds two int values.

Basic Usage with a Stream of Integers

If you already have a Stream<Integer>, you can use summingInt with a method reference that unboxes the value. For example:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); int sum = numbers.stream() .collect(Collectors.summingInt(Integer::intValue));

Here, Integer::intValue is a ToIntFunction<Integer> that returns the primitive int value of each Integer. The collector then sums those values and returns an Integer that is auto-unboxed to int in the assignment. This works for any Stream<Integer>, but if you have an IntStream directly, you would typically call sum() on it instead.

Summing a Property from a Custom Object

A more common use case is summing a numeric field from a list of domain objects. Consider a Person class with an age field:

class Person { private String name; private int age; // constructor, getters, etc. public int getAge() { return age; } }

You can sum all ages with:

List<Person> people = getPeople(); int totalAge = people.stream() .collect(Collectors.summingInt(Person::getAge));

The method reference Person::getAge is a ToIntFunction<Person> that extracts the age. This is concise and avoids writing an explicit loop. It also composes well with other collectors, such as groupingBy, where you might want the sum of a property per group.

Using summingInt with mapToInt and sum()

Java provides an alternative for summing integers: mapToInt(...).sum(). For example:

int totalAge = people.stream() .mapToInt(Person::getAge) .sum();

Both approaches produce the same result. The key difference is that mapToInt returns an IntStream, which has a built-in sum() method that operates on primitive values without any boxing. This is slightly more direct and often more readable when you only need the sum. However, summingInt becomes valuable when you are already working with Collectors—for instance, as a downstream collector in a groupingBy operation:

Map<String, Integer> totalAgeByCity = people.stream() .collect(Collectors.groupingBy(Person::getCity, Collectors.summingInt(Person::getAge)));

In such cases, you cannot use mapToInt().sum() directly because you need a Collector. So the choice depends on whether you are building a collector pipeline or simply computing a standalone sum.

Handling Null Values and Empty Streams

Collectors.summingInt returns 0 for an empty stream. This is convenient because it avoids null checks. However, the mapper function must not return null because ToIntFunction returns a primitive int. If the mapper tries to unbox a null reference, a NullPointerException is thrown at runtime. For example, if Person::getAge were to return an Integer that could be null, you would need to handle that explicitly, such as by providing a default value:

int totalAge = people.stream() .collect(Collectors.summingInt(p -> p.getAge() != null ? p.getAge() : 0));

This is a common pitfall when migrating from older code that used Integer fields. Always ensure the mapped value is non-null before using summingInt.

Performance and Overhead Considerations

The internal implementation of summingInt uses a primitive int accumulator, so it avoids boxing per element. This makes it efficient for large streams. The only boxing occurs when the final Integer result is returned. If you are summing millions of elements, the difference between summingInt and mapToInt().sum() is negligible because both avoid per-element boxing. The real performance concern is whether you are using a Stream<Integer> (which involves boxing for each element in the stream itself) versus an IntStream. If you start with an IntStream, use sum() directly. If you have a Stream<Integer>, summingInt is a clean way to unbox and sum without manually writing a loop.

One subtlety: summingInt returns an Integer, not an int. If you are summing values that might exceed Integer.MAX_VALUE, the sum will overflow silently. In such cases, consider using Collectors.summingLong or switching to a long accumulator. The same applies if you are summing many large values and want to avoid overflow.

Common Pitfalls and Edge Cases

Besides null handling, there are a few other edge cases to keep in mind. First, the stream must not contain null elements if the mapper is a method reference that would throw a NullPointerException. For example, people.stream().map(Person::getAge) would fail if any Person is null. You can filter nulls before collecting:

int totalAge = people.stream() .filter(Objects::nonNull) .collect(Collectors.summingInt(Person::getAge));

Second, summingInt is not designed for summing long or double values; use summingLong or summingDouble respectively. Third, when using parallel streams, the collector's combiner correctly adds partial sums, but you must ensure the stream is not infinite, as the reduction would never terminate.

When to Choose summingInt Over Other Summation Approaches

Deciding between summingInt, mapToInt().sum(), and a manual loop depends on the context. Use summingInt when you are already using the Collectors API, such as in a groupingBy or partitioningBy downstream, or when you need to combine multiple collectors. Use mapToInt().sum() for a simple, standalone sum where you want to avoid the collector abstraction. A manual loop is rarely necessary but might be clearer for very small collections or when you need to perform additional operations during iteration. The key is to choose the approach that matches the surrounding code style and the need for composability. If you are summing a property from a list of objects and want the most readable one-liner, summingInt is often the best fit.

java collectors summingint: Practical Usage and Code Example | RYUSLOG DEV