Writing a custom collector
Collector.of, implementing Collector directly, top-N, immutable sorted sets, batching, combiner correctness, characteristics and Java Gatherers.
On this page
🛠️ What is a custom collector?
A custom collector is your own recipe for reducing a stream into a result: it says how to create a mutable work container, add one element, combine two partial containers, and finish the result. It acts like a kitchen station card: get a bowl, add each ingredient, merge bowls if two cooks worked in parallel, then plate the dish. For example, a top-10 collector can keep a 10-item heap instead of sorting every input element.
For this lesson, we'll focus on writing collectors, not memorizing every built-in one. We'll cover Collector.of, implementing Collector as a class, a bounded top-N collector, an immutable sorted set collector, a batching collector, combiner correctness, characteristics, and Java 22+ Gatherers as the future for some stateful stream work. The general collector model is covered in Collectors deep dive.
Below the line (out of scope)
- Built-in collectors and downstream composition: Collectors deep dive
- Grouping and partitioning collectors: groupingBy and partitioningBy in depth
- Parallel stream costs and fork-join behavior: Parallel streams: when they help and hurt
Spliteratordesign: Spliterator explained
The 30-second answer
A collector has five pieces: supplier creates the mutable accumulation type A, accumulator folds one stream element into A, combiner merges two A values for parallel execution, finisher turns A into result type R, and characteristics tell the stream engine what shortcuts are safe. Collector.of(...) is best for small named recipes. Implement Collector<T,A,R> directly when the collector is reusable, has validation, or reads better as a class. The combiner must produce the same logical result as sequential accumulation, or parallel streams will be wrong. Be conservative with characteristics: do not claim IDENTITY_FINISH if your finisher copies or wraps, and do not claim CONCURRENT unless one shared accumulator can be updated safely.
What you’ll learn
- Build custom collectors with **
Collector.of** supplier, accumulator, combiner, finisher and characteristics - Implement
Collector<T,A,R>directly when a named reusable collector is clearer - Write a bounded top-N collector backed by a
PriorityQueue - Explain batching collectors and why parallel streams make them tricky
- Test combiner correctness with parallel streams instead of assuming sequential behavior
- Avoid wrong
IDENTITY_FINISH,CONCURRENTandUNORDEREDcharacteristics
01Picture it first: many bowls, one plated result
Picture two cooks preparing the same recipe from two halves of a conveyor belt. Each cook gets a bowl from the supplier, adds ingredients with the accumulator, and then the combiner pours one bowl into the other. The finisher plates the final bowl.
A custom collector creates mutable containers with a supplier, accumulates stream elements, combines partial containers in parallel, and finishes as the final result
The mental model is mutable inside, stable outside. Most collectors mutate a private work container because that is efficient. The result can still be immutable if the finisher copies or wraps it.
02Collector.of under the hood
Try it yourself first
Before reading the code, name the mutable container type and the final result type. Are they the same?
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collector;
public class CollectorOfDemo {
static <T> Collector<T, List<T>, List<T>> toUnmodifiableListLike() {
return Collector.of(
ArrayList::new,
List::add,
(left, right) -> {
left.addAll(right);
return left;
},
Collections::unmodifiableList
);
}
public static void main(String[] args) {
List<String> ids = List.of("u1", "u2", "u3").stream()
.collect(toUnmodifiableListLike());
System.out.println(ids); // [u1, u2, u3]
}
}
Premium lesson
Keep reading with the Java Pass
The rest of this lesson is part of Java One-Stop Interview Prep: in-depth explanations, diagrams, model answers and how to say them in the interview.
- Advanced example: a top-N collector backed by a heap
- Implementing Collector as a class
- Batching collectors and the parallel trap
- Characteristics: be boring and correct
- How to say it in the interview
From ₹199 · one-time payment · No auto-renewal