Back to Blog
Java

Java Collectors Counting: How to Count Stream Items

java collectors counting: Learn how to use Collectors.counting() in Java streams to count elements, group counts by key, and understand when to use count() instead.

Java StreamsCollectorsgroupingByJava 8Functional Programming
Illustration of a Java stream pipeline with a magnifying glass and bar chart representing Collectors.counting() counting elements.

When you need to count elements in a Java stream, the first method that usually comes to mind is Stream.count(). But in many stream pipelines, especially when you are already using Collectors.groupingBy() or Collectors.toMap(), you need a Collector that produces a count. That is where Collectors.counting() fits. Here is what java collectors counting looks like in practice: what Collectors.counting() returns, how to use it with grouping, and when a plain count() call is the better choice.

What Collectors.counting() Returns

Collectors.counting() returns a Collector<T, ?, Long> that counts the number of input elements. The result is a Long object, not a primitive long. This matters when you store the result in a collection such as a Map, because generic types cannot use primitives.

long total = Stream.of("a", "b", "c").count(); Long totalAsLong = Stream.of("a", "b", "c").collect(Collectors.counting());

Both expressions produce the same numeric value, but the second returns Long. In most cases the difference is irrelevant because autoboxing handles the conversion, but when you are building a Map<Integer, Long>, the collector version is the one you need.

The collector is a reducing collector. It maps every element to 1L and then sums those values. For an empty stream, it returns 0L.

Counting Within groupingBy

The most common use of Collectors.counting() is as a downstream collector for groupingBy. When you group a stream by a classifier function, you often want to know how many elements fall into each group.

List<String> words = Arrays.asList("cat", "dog", "elephant", "ant"); Map<Integer, Long> countByLength = words.stream() .collect(Collectors.groupingBy(String::length, Collectors.counting())); System.out.println(countByLength); // {3=3, 8=1}

The classifier String::length produces the group key. The downstream collector counting() counts the elements in each group. Without the downstream collector, groupingBy would produce a List<String> for each key. Adding counting() changes the value type to Long.

This pattern is common when you need a frequency distribution: counts of words by length, orders by status, or events by category.

Using counting() with partitioningBy

partitioningBy is a specialized version of groupingBy that always produces two keys: true and false. It also accepts a downstream collector.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6); Map<Boolean, Long> evenOddCount = numbers.stream() .collect(Collectors.partitioningBy(n -> n % 2 == 0, Collectors.counting())); System.out.println(evenOddCount); // {false=3, true=3}

Here counting() counts how many numbers satisfy the predicate and how many do not. This is more concise than collecting two separate streams and avoids traversing the input twice.

Collectors.counting() vs Stream.count()

When you only need the total number of elements in a stream, Stream.count() is the simpler choice.

long total = items.stream().count();

There is no reason to write items.stream().collect(Collectors.counting()) for a simple count. The collector version becomes useful when it is part of a larger collection operation.

ApproachReturn typeTypical use
stream.count()longTotal count of elements in a single stream
collect(Collectors.counting())LongCount as a standalone collector, often for compatibility
groupingBy(key, counting())Map<K, Long>Count elements per group
partitioningBy(pred, counting())Map<Boolean, Long>Count elements by a boolean condition

The table shows that counting() is rarely used alone. Its value comes from being composable with other collectors.

Performance and Runtime Behavior

Both Stream.count() and Collectors.counting() require traversing the entire stream, so their time complexity is O(n). Neither one builds an intermediate collection. counting() is implemented as a reducing collector that maps each element to 1L and sums the results, so it does not accumulate a list or set.

When you use counting() as a downstream collector inside groupingBy, the grouping operation itself creates a map. That map is the main memory cost. The counting step itself adds only a Long value per group.

For parallel streams, counting() works correctly because it has a combiner that adds the partial counts. The groupingBy collector, however, is not concurrent by default. If you are processing a large stream in parallel and need a concurrent map, use groupingByConcurrent instead.

Map<Integer, Long> countByLength = words.parallelStream() .collect(Collectors.groupingByConcurrent(String::length, Collectors.counting()));

This produces a ConcurrentMap and allows the grouping operation to be performed concurrently.

Common Edge Cases and Pitfalls

An empty stream produces a count of 0L, not null. This is important when you later read a value from a map and call longValue() on it.

Map<Integer, Long> counts = Stream.<String>empty() .collect(Collectors.groupingBy(String::length, Collectors.counting())); System.out.println(counts); // {}

If you ask for a key that does not exist, counts.get(3) returns null. You need to handle that case separately, for example with getOrDefault.

Another pitfall is using groupingBy with a classifier that can return null. The HashMap used by groupingBy does not allow null keys, so the operation throws NullPointerException. This is not specific to counting(), but it often appears when counting by a field that may be null.

List<String> words = new ArrayList<>(); words.add("cat"); words.add(null); words.stream() .collect(Collectors.groupingBy(String::length, Collectors.counting())); // NPE

If null keys are possible, filter them out before grouping or use a classifier that returns a non-null default.

When counting() Is the Wrong Choice

If you need the sum of a numeric field rather than the number of elements, use summingLong or summingInt instead of counting(). For example, to sum order totals:

long totalAmount = orders.stream() .collect(Collectors.summingLong(Order::getAmount));

counting() only tells you how many orders there are, not their total value. Similarly, if you need both the count and another aggregate, consider teeing (Java 12+) to compute both in one pass.

class OrderStats { final long count; final long total; OrderStats(long count, long total) { this.count = count; this.total = total; } } OrderStats stats = orders.stream() .collect(Collectors.teeing( Collectors.counting(), Collectors.summingLong(Order::getAmount), OrderStats::new ));

This avoids traversing the stream twice. counting() is the right tool when you need a count as a collector value, not when you need a primitive long from a simple stream.

java collectors counting: Practical Usage and Code Examples | RYUSLOG DEV