Collectors deep dive
Collector internals, the Collectors catalogue, downstream collectors, list collection choices and Java version differences.
On this page
- Picture it first: a packing station with merge lanes
- Collector under the hood: the five functions
- The Collectors catalogue: what I reach for first
- Downstream collectors: the collector inside the bucket
- toList choices: similar names, different contracts
- Characteristics and parallel collection
- How to say it in the interview
🧰 What is a Collector?
A Collector is the recipe a stream uses to turn many elements into one result container, such as a List, Set, String, statistics object or map. It acts like a packing station: one person opens a box, workers put items into it, parallel workers merge boxes, and someone may wrap the final box before shipping. For example, orders.stream().collect(Collectors.summingInt(Order::cents)) packs all order amounts into one Integer total.
For this lesson, we'll focus on the Collector API rather than every stream operation around it: supplier, accumulator, combiner, finisher, characteristics, the standard Collectors catalogue, downstream collectors, and the list-collecting methods that are easy to confuse. Interviewers ask this because it separates people who can call toList() from people who understand why groupingBy(..., counting()) works.
Below the line (out of scope)
reduceversus mutable collection: Stream.reduce and summing numbers- Optional results from
maxByandminBy: Optional in stream pipelines - The collection pattern version of grouping: Grouping and partitioning
- Writing your own collector from scratch: Writing a custom collector
The 30-second answer
A collector has five moving parts: supplier creates a fresh mutable result container, accumulator folds one stream element into that container, combiner merges two partial containers for parallel work, finisher converts the mutable container to the published result, and characteristics describe shortcuts such as IDENTITY_FINISH, UNORDERED and CONCURRENT. Most code should use the factory methods in Collectors: toList, toSet, joining, counting, numeric summaries, mapping, filtering, flatMapping, reducing, collectingAndThen and teeing. Use Stream.toList() when you want an unmodifiable list that can contain nulls, Collectors.toUnmodifiableList() when you want unmodifiable and null-rejecting, and do not rely on Collectors.toList() being mutable even though OpenJDK 21 returns an ArrayList today.
What you’ll learn
- Explain the **
Collectorcontract**: supplier, accumulator, combiner, finisher and characteristics - Choose between
Stream.toList(),Collectors.toList()andCollectors.toUnmodifiableList()with the right null and mutability rules - Use the Collectors catalogue for lists, sets, strings, numbers, summaries, reductions and teeing
- Describe how downstream collectors let
groupingBycollect counts, totals, sets or best values per bucket - Spot parallel collector bugs caused by shared mutable state or a broken combiner
- Name the Java version for
filtering,flatMapping,toUnmodifiableList,teeingandStream.toList()
01Picture it first: a packing station with merge lanes
Picture a warehouse packing station. Each lane gets an empty carton from the supplier, workers drop items into that carton with the accumulator, and a merge worker combines cartons when the stream ran in parallel. At the end, the finisher may seal the carton and print a label, or it may hand the same carton straight to you.
That is the important mental model. A collector is allowed to build with one shape and publish another shape. joining() builds with a StringBuilder style accumulator but publishes a String; collectingAndThen(toList(), List::copyOf) builds a list and then freezes the boundary.
A collector creates containers with a supplier, accumulates elements in parallel lanes, combines partial containers and runs a finisher to publish the result
Notice the parallel branch. If a collector has no valid combiner, it may pass every sequential demo and still be wrong for parallelStream(). The combiner is not optional decoration.
02Collector under the hood: the five functions
A collector is parameterized as Collector<T, A, R>. T is the stream element type, A is the mutable accumulation type, and R is the final result type. In Collectors.toList(), T is your element, A is a mutable list, and R is also a list.
Try it yourself first
Before reading on, predict which method creates the first StringJoiner, which method sees each order, and which method runs only after all elements are consumed.
import java.util.List;
import java.util.StringJoiner;
import java.util.stream.Collector;
record Order(String id, int cents) { }
public class CollectorPiecesDemo {
public static void main(String[] args) {
Collector<Order, StringJoiner, String> orderIds = Collector.of(
() -> new StringJoiner(", "),
(joiner, order) -> joiner.add(order.id()),
StringJoiner::merge,
StringJoiner::toString
);
String ids = List.of(
new Order("o-101", 1200),
new Order("o-102", 800),
new Order("o-103", 450))
.stream()
.collect(orderIds);
System.out.println(ids); // o-101, o-102, o-103
}
}
Notice four things:
- The supplier must create a fresh container. Reusing one static container is a data race waiting to happen.
- The accumulator mutates that container and usually returns nothing because the container itself is changing.
- The combiner must merge the right container into the left container and return the merged container.
- The finisher publishes the result. Here it converts
StringJoinertoString, so this collector is notIDENTITY_FINISH.
The three most common characteristics are worth saying carefully. IDENTITY_FINISH means the accumulation object can be returned as the final result with no finisher conversion. UNORDERED means the collector does not care about encounter order. CONCURRENT means multiple threads may call the accumulator on the same result container, but it only helps when the stream and collector rules allow concurrent accumulation.
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.
- The Collectors catalogue: what I reach for first
- Downstream collectors: the collector inside the bucket
- toList choices: similar names, different contracts
- Characteristics and parallel collection
- How to say it in the interview
From ₹199 · one-time payment · No auto-renewal