Collectors.toMap and working with maps
Collectors.toMap overloads, duplicate keys, null values, map suppliers, concurrent maps and entrySet stream transformations.
On this page
🗺️ What is
Collectors.toMap?
Collectors.toMap is the stream collector that turns elements into map entries by asking for a key and a value. It acts like a labeling machine at the end of a conveyor belt: each item gets a label, then Java puts the labeled value into a map. For example, a stream of users can become Map<String, User> keyed by user id.
For this lesson, we'll focus on the stream side of map work: the toMap overloads, duplicate-key policies, null-value traps, ordered and sorted suppliers, toConcurrentMap, and transformations that start from map.entrySet().stream(). We will link to Everyday Map operations for loop-based Map methods, and to Collectors deep dive for the general Collector API.
Below the line (out of scope)
- General collector mechanics: Collectors deep dive
- Grouping into buckets and downstream collectors: groupingBy and partitioningBy in depth
- Manual
Map.merge,computeIfAbsentand entry iteration: Everyday Map operations - Concurrent map internals: covered in the Concurrency module
The 30-second answer
Use Collectors.toMap(keyMapper, valueMapper) only when keys are unique and values are non-null. If two elements produce the same key, it throws IllegalStateException, in Java 21 with a message like Duplicate key u1 (attempted merging values Mina and Mira). Add the three-argument overload when duplicates are possible, for example Integer::sum, (first, second) -> first or (first, second) -> second. Add the four-argument overload when map type matters, such as LinkedHashMap::new to preserve encounter order or TreeMap::new to sort keys. Function.identity() means the whole stream element is the value. toMap rejects null values because it uses merge-style accumulation, so use a custom three-argument collect when you truly need nulls. If one key maps to many values, stop fighting toMap and use groupingBy.
What you’ll learn
- Choose the right **
Collectors.toMapoverload** for duplicate keys, order and sorted output - Explain why the two-argument
toMapthrowsIllegalStateExceptionon duplicate keys - Handle
nullvalues safely whentoMapdelegates toMap.merge - Use
Function.identity(),LinkedHashMap::new,TreeMap::newandtoConcurrentMapdeliberately - Stream over
map.entrySet()to filter, sort, invert and transform maps - Recognize when **
groupingBy** is the better collector than forcing a map merge
01Picture it first: labels, collisions and the output box
Picture a belt of user records. The key mapper prints the label on the box, the value mapper decides what goes inside, and the merge function is the tie-breaker when two boxes get the same label. The map supplier chooses the shelf: unordered HashMap, insertion-ordered LinkedHashMap, sorted TreeMap, or a concurrent map.
A stream flows through key and value mappers into a map; duplicate keys go through a merge function and the supplier chooses HashMap, LinkedHashMap or TreeMap
Notice the two decisions that candidates often blend together. Duplicate policy is not the same as map type. You can sum duplicate values into a HashMap, keep the last value in a LinkedHashMap, or reject duplicates in a TreeMap.
02toMap under the hood: the overloads you actually use
Try it yourself first
Before reading on, predict the exception from the first collector. Then decide which duplicate policy you want: fail, keep first, keep last, or combine.
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
record User(String id, String name) { }
public class ToMapDuplicateDemo {
public static void main(String[] args) {
List<User> users = List.of(
new User("u1", "Mina"),
new User("u2", "Ravi"),
new User("u1", "Mira")
);
try {
users.stream().collect(Collectors.toMap(User::id, User::name));
} catch (IllegalStateException duplicate) {
System.out.println(duplicate.getMessage());
// Duplicate key u1 (attempted merging values Mina and Mira)
}
Map<String, User> firstUserById = users.stream().collect(
Collectors.toMap(
User::id,
Function.identity(),
(first, second) -> first
)
);
System.out.println(firstUserById.get("u1").name()); // Mina
}
}
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: map suppliers, null values and concurrent maps
- Working with existing maps using streams
- When groupingBy is the better choice
- How to say it in the interview
From ₹199 · one-time payment · No auto-renewal