Home/Learn/Java A–Z/Stream Collectors

Stream Collectors

Intermediate
Streams & Functional Java

Use Collectors to accumulate stream elements into lists, maps, grouped structures, joined strings, and custom containers.

Overview

The Collectors utility class provides factory methods for the most common reduction strategies. collect(Collectors.toList()) is the most-used terminal operation, but Collectors also supports groupingBy (build a Map<K,List<V>>), partitioningBy (split into true/false), joining (concatenate strings), counting, summing, averaging, toMap, toUnmodifiableList, and teeing (Java 12). Downstream collectors let you chain operations: groupingBy then counting, groupingBy then mapping, etc.

toList, toSet, toMap, joining

toList(), toSet(), toUnmodifiableList() collect elements into standard collections. toMap requires a key extractor and a value extractor; if keys can collide, provide a merge function as the third argument. joining() concatenates strings with optional delimiter, prefix, and suffix.

BasicCollectors.java
import java.util.*;
import java.util.stream.*;

public class BasicCollectors {
    record Person(String name, String city, int age) {}

    public static void main(String[] args) {
        List<Person> people = List.of(
            new Person("Alice", "NYC", 30),
            new Person("Bob",   "LA",  25),
            new Person("Carol", "NYC", 35),
            new Person("Dave",  "LA",  28)
        );

        // toList (Java 16 shorthand), toSet, toUnmodifiableList
        List<String> names = people.stream()
            .map(Person::name)
            .collect(Collectors.toList());
        System.out.println(names);

        // toMap — key must be unique or merge function required
        Map<String, Integer> nameToAge = people.stream()
            .collect(Collectors.toMap(Person::name, Person::age));
        System.out.println(nameToAge);

        // toMap with merge function for duplicate keys
        Map<String, Long> cityCount = people.stream()
            .collect(Collectors.toMap(
                Person::city,
                p -> 1L,
                Long::sum));
        System.out.println(cityCount); // {NYC=2, LA=2}

        // joining — concatenate strings
        String csv = people.stream()
            .map(Person::name)
            .collect(Collectors.joining(", ", "[", "]"));
        System.out.println(csv); // [Alice, Bob, Carol, Dave]

        // counting, summingInt, averagingInt
        long total = people.stream().collect(Collectors.counting());
        int  sumAge = people.stream().collect(Collectors.summingInt(Person::age));
        double avg  = people.stream().collect(Collectors.averagingInt(Person::age));
        System.out.println(total + " | " + sumAge + " | " + avg); // 4 | 118 | 29.5
    }
}

groupingBy & partitioningBy

groupingBy(classifier) produces Map<K, List<V>>. A downstream collector as the second argument transforms the value list: counting(), mapping(), toSet(), joining(), summarizingInt(), etc.

partitioningBy(Predicate) is a specialised groupingBy with boolean keys — always produces a Map<Boolean, List<T>> with both true and false keys present.

GroupingCollectors.java
import java.util.*;
import java.util.stream.*;

public class GroupingCollectors {
    record Employee(String name, String dept, double salary) {}

    public static void main(String[] args) {
        List<Employee> emps = List.of(
            new Employee("Alice", "Engineering", 95000),
            new Employee("Bob",   "Engineering", 88000),
            new Employee("Carol", "Marketing",   72000),
            new Employee("Dave",  "Marketing",   68000),
            new Employee("Eve",   "Engineering", 102000)
        );

        // groupingBy — Map<dept, List<Employee>>
        Map<String, List<Employee>> byDept =
            emps.stream().collect(Collectors.groupingBy(Employee::dept));
        byDept.forEach((d, list) ->
            System.out.println(d + ": " + list.stream().map(Employee::name).toList()));

        // groupingBy + counting downstream
        Map<String, Long> countByDept =
            emps.stream().collect(Collectors.groupingBy(Employee::dept, Collectors.counting()));
        System.out.println(countByDept); // {Engineering=3, Marketing=2}

        // groupingBy + averagingDouble downstream
        Map<String, Double> avgSalary = emps.stream().collect(
            Collectors.groupingBy(Employee::dept, Collectors.averagingDouble(Employee::salary)));
        System.out.println(avgSalary);

        // groupingBy + mapping downstream — Map<dept, List<name>>
        Map<String, List<String>> namesByDept = emps.stream().collect(
            Collectors.groupingBy(Employee::dept,
                Collectors.mapping(Employee::name, Collectors.toList())));
        System.out.println(namesByDept);

        // partitioningBy — split into true/false
        Map<Boolean, List<Employee>> highEarners = emps.stream()
            .collect(Collectors.partitioningBy(e -> e.salary() > 80000));
        System.out.println("High: " + highEarners.get(true).stream().map(Employee::name).toList());
        System.out.println("Low:  " + highEarners.get(false).stream().map(Employee::name).toList());
    }
}

teeing, toUnmodifiable & Custom Collectors

Collectors.teeing (Java 12) runs two collectors in parallel on the same stream and merges their results — useful when you need two aggregations in one pass (e.g., min and max simultaneously).

Collectors.toUnmodifiableList/Set/Map return immutable views. For full custom collectors, implement the Collector<T,A,R> interface with supplier, accumulator, combiner, finisher, and characteristics.

AdvancedCollectors.java
import java.util.*;
import java.util.stream.*;
import java.util.function.Function;

public class AdvancedCollectors {
    public static void main(String[] args) {
        List<Integer> nums = List.of(3, 1, 4, 1, 5, 9, 2, 6);

        // teeing (Java 12) — two collectors, one merge
        record MinMax(int min, int max) {}
        MinMax minmax = nums.stream().collect(
            Collectors.teeing(
                Collectors.minBy(Integer::compareTo),
                Collectors.maxBy(Integer::compareTo),
                (min, max) -> new MinMax(min.orElseThrow(), max.orElseThrow())
            ));
        System.out.println(minmax); // MinMax[min=1, max=9]

        // toUnmodifiableList — immutable result
        List<Integer> immutable = nums.stream()
            .filter(n -> n > 3)
            .collect(Collectors.toUnmodifiableList());
        // immutable.add(1); // UnsupportedOperationException

        // Frequency map using groupingBy + counting
        Map<Integer, Long> freq = nums.stream()
            .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
        System.out.println(freq); // {1=2, 2=1, 3=1, 4=1, 5=1, 6=1, 9=1}

        // summarizingInt — get count/sum/min/max/average in one pass
        IntSummaryStatistics stats = nums.stream()
            .collect(Collectors.summarizingInt(Integer::intValue));
        System.out.println(stats);
        // IntSummaryStatistics{count=8, sum=31, min=1, average=3.875, max=9}
    }
}

Interactive Visualization

.source()
.filter()
.map()
.sorted()
.collect()
1
2
3
4
5
6
7
8
stream.filter(n → n%2==0).map(n → n*n).sorted().collect(toList())
Source: a stream of integers [1, 2, 3, 4, 5, 6, 7, 8].
1 / 5

Key Points to Remember

  • groupingBy produces Map<K, List<V>>; add a downstream collector to transform the values
  • partitioningBy always produces Map<Boolean, List<T>> — both true and false keys exist
  • joining(delimiter, prefix, suffix) is the clean way to build CSV/bracketed strings
  • toMap with three args (key, value, mergeFunction) handles duplicate keys gracefully
  • teeing (Java 12) runs two collectors in one pass — great for simultaneous min/max
  • summarizingInt/Long/Double returns count, sum, min, max, and average in one collector

Practice Stream Collectors in the Playground

Run and modify code directly in your browser - no setup needed.

Interview Questions

Sign in to ask Aria
1

What does Collectors.groupingBy() return?

MediumAmazon
2

What is the difference between groupingBy and partitioningBy?

MediumGoogle
3

How do you handle duplicate keys in Collectors.toMap()?

MediumMicrosoft
4

How would you count the frequency of each element in a list using Streams?

EasyTCS
5

What does Collectors.teeing() do?

HardOracle

Ask Aria about Stream Collectors

Your personal AI tutor — ask anything about this concept