List to Map in Java Streams — Collectors.toMap, Duplicates, and Map Suppliers
Fast recall
Quick revision
Core answer
users.stream().collect(toMap(User::getId, u -> u)) produces Map. Critical: this throws `IllegalStateException` on duplicate keys by default — the classic gotcha when the key isn't actually unique. Use the three-arg overload toMap(keyMapper, valueMapper, mergeFunction) to handle duplicates: (existing, dup) -> existing (keep first), (existing, dup) -> dup (keep last), or a custom merge like Integer::sum for counts. The four-arg overload toMap(keyMapper, valueMapper, mergeFunction, mapSupplier) lets you pick the map impl — LinkedHashMap::new to preserve insertion order, TreeMap::new for sorted keys, () -> new HashMap<>(initialCapacity) to pre-size. The default returned map is a `HashMap` — no order guarantees. For one-to-many mappings (multiple values per key), use `Collectors.groupingBy` instead of toMap. For immutable output, use `toUnmodifiableMap` (Java 10+). Common patterns: identity map (toMap(User::getId, u -> u)), field extraction (toMap(User::getEmail, User::getName)), counting via merge (toMap(Word::value, w -> 1, Integer::sum)), and sorted lookup table (toMap(k, v, merge, TreeMap::new)). Watch for: null values throw NPE in toMap (Java 8 quirk — unlike HashMap.put(null) which works); use groupingBy + Collectors.mapping if values can be null. Java 16+ Stream.toList() is for lists, not maps — there's no Stream.toMap() shortcut.Whether the candidate knows the four overloads of `toMap`, the duplicate-key gotcha, the null-value NPE quirk, and when to choose `toMap` vs `groupingBy`
Using `toMap(k, v)` without a merge function and being surprised by `IllegalStateException` on duplicates. Forgetting the null-value NPE quirk. Using `toMap` for one-to-many (should be `groupingBy`)
Show the four-arg overload with `LinkedHashMap`/`TreeMap`, mention `toUnmodifiableMap` (Java 10+), discuss the historical reason for the duplicate-key strict default, demonstrate `groupingBy(k, mapping(v, toList()))` as the one-to-many alternative
Interview-ready guide
Interview answer
Use `Collectors.toMap(keyMapper, valueMapper)` to convert a list to a map. There are four overloads, and you almost always need to know about all of them.
Four overloads
toMap(k, v)— throws on duplicate keystoMap(k, v, mergeFunction)— collide-safe; pick first/last/combinetoMap(k, v, merge, mapSupplier)— pick the Map impl: LinkedHashMap, TreeMap, EnumMap, pre-sized HashMaptoUnmodifiableMap(k, v)— Java 10+ for immutable result (no merge/supplier overloads)
The duplicate-key gotcha — the most common mistake
- Two-arg
toMapis strict: throwsIllegalStateExceptionon duplicate keys - Historical: design committee chose fail-loud over silent data loss
- Without merge, the collector can't know whether to keep first, last, or combine
Three merge strategies
(existing, dup) -> existing— keep first(existing, dup) -> dup— keep lastInteger::sum,String::concat, custom op — combine
The null-value NPE quirk
toMapthrows NPE if the value mapper returns null- Even though
HashMap.put(key, null)works fine - Internal implementation uses
Map.mergewhich forbids null values - Workarounds:
- Filter null values before collecting
- Replace null with a sentinel
- Use
Optional.ofNullable(...)as the value - Build map manually with a for-loop
Picking the Map impl — four-arg overload
LinkedHashMap::new— insertion order (encounter order in stream)TreeMap::new— sorted by keyEnumMap::new— most efficient for enum keys() -> new HashMap<>(initialCap)— pre-sized to avoid resize- For parallel — use
toConcurrentMapwhich givesConcurrentHashMap
`toUnmodifiableMap` (Java 10+)
- Immutable result; throws on mutation
- Throws NPE on null keys or values
- No merge or supplier overload
- For merge + immutable, wrap with
Collections.unmodifiableMapaftertoMap - Or use
collectingAndThen(toMap(...), Collections::unmodifiableMap)
`toMap` vs `groupingBy` — the decision
Use toMap when
- Key is unique
- You want flat
Map(one value per key) - Lookup tables, indexing by ID
Use groupingBy when
- Key is not unique — multiple values per key
- You want
Mapor aggregated values> - Histograms, partitioning, aggregations
Easy mistake — using toMap for a not-actually-unique key, hitting IllegalStateException in production
- If you're at all unsure: add a merge function defensively OR switch to
groupingBy
Counting pattern — use groupingBy
groupingBy(Function.identity(), counting())— idiomatic- Don't fight it with
toMap(id, w -> 1L, Long::sum)— less readable
Boolean partitioning — use partitioningBy
partitioningBy(predicate)— returnsMapwith both keys present> - Don't use toMap for this
`Function.identity()`
- The canonical 'use the element as-is' mapper
toMap(User::getId, Function.identity())— map of users by ID
Performance
toMapslightly faster thangroupingByfor unique keys (no list allocation)- For typical N (< 10k), negligible
- Pre-size HashMaps for very large input to avoid resize
- For very-parallel:
toConcurrentMapwrites directly toConcurrentHashMap
No Stream.toMap()
- Java 16+ added
Stream.toList()for lists - No equivalent for maps — always go through
Collectors.toMap
Recommendation: Use `Collectors.toMap(keyMapper, valueMapper)` as your default for converting a list to a map of unique keys; always provide a merge function when there's any chance of duplicate keys to avoid IllegalStateException in production. Pick `(existing, dup) -> existing` for keep-first, `(existing, dup) -> dup` for keep-last, or a combine like Integer::sum for aggregations. Use the four-arg overload with LinkedHashMap::new for insertion order, TreeMap::new for sorted output, or EnumMap::new for enum keys. For immutable results, use `toUnmodifiableMap` (Java 10+); for immutable + merge, wrap with collectingAndThen(toMap(...), Collections::unmodifiableMap). Watch the null-value NPE quirk — filter or replace nulls before collecting. For one-to-many (multiple values per key), don't fight toMap: switch to `groupingBy` which is the right tool. For counting, prefer `groupingBy(k, counting())` over toMap with merge — it's idiomatic. For boolean splitting, use `partitioningBy`. The mental shortcut: toMap = unique keys, flat values; groupingBy = many-to-one, aggregate values; partitioningBy = boolean two-bucket. Pick the right tool, always handle duplicates and nulls explicitly.
List to Map in Java Streams — `Collectors.toMap`, Duplicates, and Map Suppliers
Direct Answer
Use Collectors.toMap(keyMapper, valueMapper) to convert a list to a map: users.stream().collect(toMap(User::getId, u -> u)) produces Map<Long, User>. Critical: this throws IllegalStateException on duplicate keys by default — the classic gotcha when the key isn't actually unique. Use the three-arg overload toMap(keyMapper, valueMapper, mergeFunction) to handle duplicates: (existing, dup) -> existing (keep first), (existing, dup) -> dup (keep last), or a custom merge like Integer::sum for counts. The four-arg overload toMap(keyMapper, valueMapper, mergeFunction, mapSupplier) lets you pick the map impl — LinkedHashMap::new to preserve insertion order, TreeMap::new for sorted keys, () -> new HashMap<>(initialCapacity) to pre-size. The default returned map is a HashMap — no order guarantees. For one-to-many mappings (multiple values per key), use Collectors.groupingBy instead of toMap. For immutable output, use toUnmodifiableMap (Java 10+). Common patterns: identity map (toMap(User::getId, u -> u)), field extraction (toMap(User::getEmail, User::getName)), counting via merge (toMap(Word::value, w -> 1, Integer::sum)), and sorted lookup table (toMap(k, v, merge, TreeMap::new)). Watch for: null values throw NPE in toMap (Java 8 quirk — unlike HashMap.put(null) which works); use groupingBy + Collectors.mapping if values can be null. Java 16+ Stream.toList() is for lists, not maps — there's no Stream.toMap() shortcut.
Common Mistakes & Pitfalls
Using toMap(k, v) without a merge function and being surprised by IllegalStateException on duplicates. Forgetting the null-value NPE quirk. Using toMap for one-to-many (should be groupingBy)
Check your understanding
Answer these without looking back at the article.
- Why does `Collectors.toMap` throw on null values when `HashMap.put(null)` works?
- What's the difference between the two-arg and three-arg `toMap`?
- When should you use `toMap` vs `groupingBy`?
- How do you preserve insertion order when collecting to a Map?
- What's `Collectors.toUnmodifiableMap` and how does it differ from `toMap`?
Related interview prep categories
1Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.