Stream Collectors — joining, reducing, collectingAndThen, toUnmodifiableList
Fast recall
Quick revision
Core answer
joining(delim, prefix, suffix) = String concat. groupingBy(key) = Map> . groupingBy(key, downstream) = nested aggregation. partitioningBy(pred) = Map. counting(), summingInt(), averagingInt(). collectingAndThen(collector, finisher) = post-process result. toUnmodifiableList() (Java 10+). toMap() throws IllegalStateException on duplicate keys — always provide merge function for safety.Whether the candidate knows collectors beyond toList/toMap — specifically joining, reducing, collectingAndThen, and the unmodifiable variants. The interviewer wants to see practical usage, not API memorization
Using Collectors.toList() and assuming the result is unmodifiable — it returns a mutable ArrayList. Using reduce() when collect() is appropriate — reduce with string concatenation creates O(n²) intermediate strings. Duplicate key errors with toMap() when keys aren't unique
Know that toUnmodifiableList() (Java 10) returns a truly unmodifiable list that throws on add/remove. Explain collectingAndThen as a post-processing wrapper — collect into a list then wrap as unmodifiable. Show groupingBy with a downstream collector like counting() or mapping()
Interview-ready guide
Interview answer
Beyond toList() and toMap(), the collectors that matter in production are joining(), groupingBy() with downstream collectors, and collectingAndThen().
`joining()`
- Concatenates strings using
StringBuilder— efficient and supports delimiter + prefix/suffix joining(", ", "[", "]")produces[a, b, c]- Using
reduce()with+for string building is O(n²) — always usejoining()instead
`groupingBy()` with downstream collectors
groupingBy(Order::getStatus)→Map> - Add
counting()downstream → counts per status - Add
mapping(Order::getId, toList())downstream → IDs per status - The downstream collector replaces the default
toList()with any other collector
Mutability and safety
toList()returns a mutableArrayList— callers can add/removetoUnmodifiableList()(Java 10+) throws on mutation; note: also rejects null elementscollectingAndThen(toList(), Collections::unmodifiableList)is the Java 8 equivalenttoMap()throwsIllegalStateExceptionon duplicate keys with no useful message — always provide a merge function in production code
I recommend returning unmodifiable collections from all API methods — it prevents accidental caller mutations and makes the contract explicit.
Stream Collectors — joining, reducing, collectingAndThen, toUnmodifiableList
Direct Answer
joining(delim, prefix, suffix) = String concat. groupingBy(key) = Map<K, List<V>>. groupingBy(key, downstream) = nested aggregation. partitioningBy(pred) = Map<Boolean, List>. counting(), summingInt(), averagingInt(). collectingAndThen(collector, finisher) = post-process result. toUnmodifiableList() (Java 10+). toMap() throws IllegalStateException on duplicate keys — always provide merge function for safety.
Common Mistakes & Pitfalls
Using Collectors.toList() and assuming the result is unmodifiable — it returns a mutable ArrayList. Using reduce() when collect() is appropriate — reduce with string concatenation creates O(n²) intermediate strings. Duplicate key errors with toMap() when keys aren't unique
Check your understanding
Answer these without looking back at the article.
- What happens when you use toMap() and there are duplicate keys — how do you handle it safely?
- How do you use groupingBy with a downstream collector to get a count or sum per group?
- What is the difference between partitioningBy and groupingBy?
- How does collectingAndThen work — give an example of wrapping a result in an unmodifiable list?
- How do you collect into a specific Map implementation like LinkedHashMap instead of the default HashMap?
Related interview prep categories
1Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.