Course contents

Writing a custom collector

Collector.of, implementing Collector directly, top-N, immutable sorted sets, batching, combiner correctness, characteristics and Java Gatherers.

PremiumGood to knowLesson10 min readSeniorReviewed Oct 2026 · Java 21
On this page
  1. Picture it first: many bowls, one plated result
  2. Collector.of under the hood
  3. Advanced example: a top-N collector backed by a heap
  4. Implementing Collector as a class
  5. Batching collectors and the parallel trap
  6. Characteristics: be boring and correct
  7. How to say it in the interview

🛠️ 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)

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, CONCURRENT and UNORDERED characteristics

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.

Fig. 1

A custom collector creates mutable containers with a supplier, accumulates stream elements, combines partial containers in parallel, and finishes as the final result

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

Practise this topicCollectors: interview questions & scenarios
Further reading (5)