Codectionary / Developer documentation / Java

Collectors

Collectors, used with the stream terminal operation collect(), provide ready-made ways to accumulate stream elements into a final result - a List, Set, Map, a joined String, or grouped/partitioned data. The java.util.stream.Collectors class offers factory methods like toList(), joining(), groupingBy(), and counting() that cover the vast majority of real-world aggregation needs.

Syntax

stream.collect(Collectors.methodName())

Examples

Collecting to a List, Set, or Map

The most common collector operations.

import java.util.List;
import java.util.stream.Collectors;

List<String> names = List.of("Fola", "Zain", "Jamal", "Fola");

List<String> asList = names.stream().distinct().collect(Collectors.toList());
System.out.println(asList);  // [Fola, Zain, Jamal]

var asMap = names.stream()
    .distinct()
    .collect(Collectors.toMap(name -> name, String::length));
System.out.println(asMap);  // {Fola=4, Zain=4, Jamal=5}

joining(): Building a String

Combining stream elements into a single delimited string.

import java.util.List;
import java.util.stream.Collectors;

List<String> names = List.of("Fola", "Zain", "Jamal");

String joined = names.stream().collect(Collectors.joining(", "));
System.out.println(joined);  // Fola, Zain, Jamal

String withBrackets = names.stream().collect(Collectors.joining(", ", "[", "]"));
System.out.println(withBrackets);  // [Fola, Zain, Jamal]

groupingBy(): Grouping Elements

Splitting a stream into groups based on a classifier function, producing a Map.

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

record Student(String name, String grade) {}

List<Student> students = List.of(
    new Student("Fola", "A"),
    new Student("Zain", "B"),
    new Student("Jamal", "A"),
    new Student("Amir", "C")
);

Map<String, List<Student>> byGrade = students.stream()
    .collect(Collectors.groupingBy(Student::grade));

System.out.println(byGrade.get("A"));  // [Student[name=Fola, grade=A], Student[name=Jamal, grade=A]]

counting() and Downstream Collectors

Combining groupingBy() with a downstream collector like counting() for summary statistics.

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

List<String> words = List.of("apple", "banana", "avocado", "blueberry", "cherry");

Map<Character, Long> countByFirstLetter = words.stream()
    .collect(Collectors.groupingBy(w -> w.charAt(0), Collectors.counting()));

System.out.println(countByFirstLetter);  // {a=2, b=2, c=1}

Best practices

  • Use Collectors.toList() (or the shorter .toList() method directly) as the default when you just need a plain result list
  • Use Collectors.groupingBy() instead of manually building a Map<K, List<V>> with a loop - it handles the bucketing logic for you
  • Combine groupingBy() with a downstream collector (counting(), summingInt(), etc.) to compute per-group statistics in one pass
  • Use Collectors.joining() with a delimiter for building comma or newline-separated output instead of a manual StringBuilder loop

At a glance

Purpose
General-purpose application development
File extension
.java
Runs in
Java Virtual Machine
Usually used with
JDK and Java libraries

Specifications & further reading

Related Java documentation

Lambda Expressions
A lambda expression is a compact way to represent an anonymous function - a block of code that can be passed around as a value. Lambdas work with functional interfaces (interfaces with exactly one abstract method), letting you replace a full anonymous class implementation with a short, focused expression. Lambdas are the foundation that makes Java's Stream API and functional-style programming possible.
Stream Basics (map, filter, forEach)
The Stream API, introduced in Java 8, provides a functional, declarative way to process sequences of elements from collections, arrays, or other sources. A stream pipeline consists of a source, zero or more intermediate operations (like filter and map, which are lazy and return a new stream), and exactly one terminal operation (like forEach or collect) that actually triggers processing.
Stream Terminal Operations
Terminal operations are what actually trigger a stream pipeline to execute and produce a final result, consuming the stream in the process - after a terminal operation runs, the stream cannot be reused. Common terminal operations include reduce() for combining elements into a single value, count(), sum-style operations for numeric streams, anyMatch()/allMatch() for boolean checks, and findFirst() for retrieving a single element.
Optional
Optional<T> is a container object that may or may not hold a non-null value, designed to make the possibility of 'no result' explicit in a method's return type, reducing NullPointerExceptions. Rather than returning null and hoping callers remember to check for it, a method returning Optional<T> signals clearly that a value might be absent, and provides safe methods like isPresent(), ifPresent(), and orElse() for handling both cases.