skip to content

How do you control which concrete Map type Collectors.toMap returns (e.g. a TreeMap or LinkedHashMap), and what does the 4-argument Supplier overload require?

level: middleimportance: should knowfreq 48%

answer

  1. 4-arg toMap(key, value, merge, mapSupplier)
  2. mapSupplier = Supplier<Map>, e.g. TreeMap::new / LinkedHashMap::new
  3. No 3-arg+supplier shortcut: supplier always comes WITH a merge fn
  4. Supplied map IS the result (typed, mutable)
  5. Parallel + thread-safe: prefer toConcurrentMap

basics

~10 s

Use the four-argument toMap(keyMapper, valueMapper, mergeFunction, mapSupplier). The fourth argument is a supplier like TreeMap::new that creates the map you want. You must also pass a merge function with this overload.

solid answer

~40 s

The two- and three-argument toMap overloads return a Map of unspecified type (a HashMap today), so insertion order and sorting are not guaranteed. To pick the concrete type, use the four-argument overload toMap(keyMapper, valueMapper, mergeFunction, mapSupplier). The mapSupplier is a Supplier<M extends Map> — e.g. TreeMap::new for sorted-by-key, LinkedHashMap::new to preserve encounter order, or EnumMap::new for enum keys. Crucially, this overload has no three-argument-plus-supplier shortcut: once you supply a map type you must also supply the merge function, so you always specify both. The supplier is called to create the result map, which the collector fills. For a thread-safe accumulator in a parallel stream you would prefer Collectors.toConcurrentMap, enabling a concurrent reduction.

code

java · 7 lines
java
// Sorted by key, summing collisions
Map<String, Integer> sorted = items.stream().collect(Collectors.toMap(
    Item::name, Item::count, Integer::sum, TreeMap::new));

// Preserve encounter order, last-wins
Map<String, Integer> ordered = items.stream().collect(Collectors.toMap(
    Item::name, Item::count, (a, b) -> b, LinkedHashMap::new));

go deeper

for a junior

Knows you can pass TreeMap::new or LinkedHashMap::new as a last argument to control the map type.

for a middle

Uses the 4-arg overload correctly, knows it requires a merge function too, and knows toCollection(Supplier) for sets/lists.

for a senior

Explains the unspecified-type contract of the shorter overloads, picks EnumMap/Tree/Linked appropriately, and knows toConcurrentMap for parallel reductions.

for a principal

Reasons about ordering and concurrency guarantees as API contracts, standardizes map-type choices for predictable iteration/serialization, and weighs concurrent vs merging reductions for performance.

## The problem: unspecified map type The `toMap` overloads with two and three arguments return a `Map` whose **concrete implementation is unspecified** — in practice a `HashMap`. A `HashMap` gives you *no ordering guarantee*: you cannot rely on insertion order or sorted keys. When you need a particular kind of map, you must say so explicitly. ## The four-argument overload ```java Map<K,V> toMap(Function<T,K> keyMapper, Function<T,V> valueMapper, BinaryOperator<V> mergeFunction, Supplier<M> mapSupplier) // M extends Map<K,V> ``` The new fourth argument, **`mapSupplier`**, is a **`Supplier`** — a no-argument function that *produces* the empty map the collector will fill. A constructor reference is the usual form: ```java // sorted by key Map<String, Integer> sorted = items.stream().collect(Collectors.toMap( Item::name, Item::count, Integer::sum, TreeMap::new)); // preserve encounter order Map<String, Integer> ordered = items.stream().collect(Collectors.toMap( Item::name, Item::count, (a, b) -> b, LinkedHashMap::new)); // enum-keyed, compact map Map<Status, Long> byStatus = items.stream().collect(Collectors.toMap( Item::status, i -> 1L, Long::sum, () -> new EnumMap<>(Status.class))); ``` ## You must also supply a merge function There is **no** `toMap(keyMapper, valueMapper, mapSupplier)` overload. The API deliberately couples the supplier with the merge function: if you want to choose the map type, you are forced to also decide how to handle duplicate keys. So the four-arg call always has both. (If duplicates are impossible you can still pass a throwing merge or `(a, b) -> b`, but you cannot omit it.) ## How the supplier is used The supplier is invoked to create the **accumulator map** the reduction writes into; for a sequential stream it is essentially called once and that map becomes the result. The collector does not return a copy — the supplied map *is* the result, so it is mutable and of exactly the type you chose. This means, unlike the 2-arg form, you get a firm guarantee about the concrete type and mutability. ## Common map choices - **`TreeMap::new`** — keys kept sorted (natural order or a comparator-configured tree). - **`LinkedHashMap::new`** — preserves the stream's encounter order. - **`EnumMap::new`** — compact, fast map keyed by an enum. - **`ConcurrentHashMap::new`** — thread-safe; but for parallel streams prefer the dedicated `Collectors.toConcurrentMap`, which performs a true *concurrent reduction* (one shared map across threads) rather than the merge-of-partials a plain `toMap` does. ## Sets parallel The same idea exists for collections generally via `Collectors.toCollection(Supplier)` — e.g. `toCollection(TreeSet::new)` to collect into a sorted set, since `toSet()` likewise gives an unspecified (`HashSet`) type.

  • Why is there no three-argument toMap that takes only a map supplier?
    The API couples the supplier with a merge function: choosing a map type forces you to also decide duplicate-key handling, so the supplier overload always includes the merge argument.
  • How do you collect into a TreeSet instead of a default HashSet?
    Use Collectors.toCollection(TreeSet::new); toSet() gives an unspecified type, so toCollection with a supplier lets you choose the sorted-set implementation.

saying these in an interview costs you the question

  • Expecting a toMap(keyMapper, valueMapper, mapSupplier) overload — it does not exist
  • Assuming the 2-arg toMap result is sorted or insertion-ordered
  • Using a plain HashMap supplier in a parallel stream and expecting concurrency benefits (use toConcurrentMap)
  • Thinking the supplier is called per element rather than to create the accumulator

context