JavaCollectors

Collectors

The Collectors class, in java.util.stream, is a utility class of static factory methods that produce Collector instances — the objects that .collect() uses to gather a stream's elements into a final result. Almost every real-world stream pipeline ends with .collect(Collectors.someMethod()).

Common collectors

Collector

Produces

Collectors.toList()

A List containing the stream elements

Collectors.toSet()

A Set containing the stream elements (duplicates removed)

Collectors.toMap(keyFn, valueFn)

A Map built from a key function and a value function

Collectors.joining(delimiter)

A single String, joining elements with the given delimiter

Collectors.groupingBy(classifierFn)

A Map of classifier results to Lists of matching elements

Collectors.counting()

A Long count — typically paired with groupingBy as a downstream collector

Collectors.summingInt(fn) / .averagingInt(fn)

A summed or averaged int/double value

Basic collectors in action

toList, toSet, joining, and toMap

Java
List<String> names = List.of("Alice", "Bob", "Charlie", "Dave");

List<String> asList = names.stream()
    .filter(n -> n.length() > 3)
    .collect(Collectors.toList());
// [Alice, Charlie, Dave]

String joined = names.stream()
    .collect(Collectors.joining(", ", "[", "]"));
// [Alice, Bob, Charlie, Dave]

Map<String, Integer> nameLengths = names.stream()
    .collect(Collectors.toMap(name -> name, String::length));
// {Alice=5, Bob=3, Charlie=7, Dave=4}
A worked example: groupingBy

Collectors.groupingBy() is one of the most useful, commonly-reached-for collectors in real code — it buckets stream elements into a Map keyed by whatever “classifier” function you give it, with each bucket holding a List of the elements that produced that key.

Grouping employees by department

Java
record Employee(String name, String department, double salary) {}

List<Employee> employees = List.of(
    new Employee("Alice", "Engineering", 95000),
    new Employee("Bob", "Sales", 62000),
    new Employee("Charlie", "Engineering", 110000),
    new Employee("Dave", "Sales", 58000),
    new Employee("Eve", "Marketing", 71000)
);

Map<String, List<Employee>> byDepartment = employees.stream()
    .collect(Collectors.groupingBy(Employee::department));

// {Engineering=[Alice, Charlie], Sales=[Bob, Dave], Marketing=[Eve]}
byDepartment.forEach((dept, staff) ->
    System.out.println(dept + ": " + staff.size() + " employees"));

groupingBy also accepts a second, downstream collector, which processes each group's elements further instead of just collecting them into a List — for example, counting each group or summing a field.

groupingBy with a downstream collector

Java
Map<String, Long> countByDepartment = employees.stream()
    .collect(Collectors.groupingBy(Employee::department, Collectors.counting()));
// {Engineering=2, Sales=2, Marketing=1}

Map<String, Double> totalSalaryByDepartment = employees.stream()
    .collect(Collectors.groupingBy(Employee::department, Collectors.summingDouble(Employee::salary)));
// {Engineering=205000.0, Sales=120000.0, Marketing=71000.0}
Note
`Collectors.toMap()` throws `IllegalStateException` if the key function produces a duplicate key. If duplicate keys are possible, pass a third argument — a merge function like `(existing, replacement) -> existing` — telling `toMap()` how to resolve the collision.
  • Collectors is a factory class of Collector implementations, used with .collect().

  • toList(), toSet(), toMap(), and joining() cover the most common simple conversions.

  • groupingBy() buckets elements by a classifier function into a Map of Lists — a very common real-world pattern.

  • A second, downstream collector can be passed to groupingBy() to reduce each group instead of collecting it as a List.