Java Streams: Convert List into Map
Introduction
Converting a List into a Map is one of the most common operations when working with Java Streams. Maps are data structures composed of key-value pairs where each key is unique, allowing for fast lookups without iterating through the entire collection.
In this post, I'll cover everything you need to know about converting a List to a Map using Collectors.toMap():
- Basic conversion when keys are unique
- Handling duplicate keys with merge functions
- Maintaining insertion order with LinkedHashMap
- Sorting keys with TreeMap
- Creating immutable maps with toUnmodifiableMap()
- Handling null values and avoiding NullPointerException
For the examples, I'll use a Repository record:
public record Repository(String name, String fullName, String description, boolean fork) {}Tip
If you're using a version before Java 16, you can use a traditional class with getters instead.
List to Map with no duplicate keys
When you're certain that the List contains no duplicate keys, use the basic two-argument version of Collectors.toMap().
The following snippet shows the basic conversion:
repos.stream().collect(Collectors.toMap(Repository::name, Function.identity()));The first step is to convert the list into a Stream and then collect the results using Collectors.toMap().
For the Map key, we use Repository::name (a method reference equivalent to r -> r.name()). For the Map value, we use Function.identity() which returns the same element (equivalent to r -> r).
Important
If the list contains duplicate keys, this will throw an IllegalStateException. See the next section for handling duplicates.
List to Map with duplicate keys
When the list might contain duplicate keys, use the three-argument version of toMap() that includes a merge function.
The following snippet shows how to handle duplicates:
repos.stream().collect(Collectors.toMap(
Repository::name,
Function.identity(),
(existing, replacement) -> replacement
));The third parameter is a BinaryOperator that receives both values when a duplicate key is encountered. In this example, we keep the newer value (replacement), but you could:
- Keep the first value:
(existing, replacement) -> existing - Combine values:
(existing, replacement) -> mergeRepositories(existing, replacement) - Throw an exception:
(existing, replacement) -> { throw new IllegalStateException("Duplicate key"); }
List to Map preserving insertion order
By default, Collectors.toMap() creates a HashMap, which doesn't preserve insertion order. To maintain the order of elements from the original list, use a LinkedHashMap.
The following snippet shows how to preserve insertion order:
LinkedHashMap<String, Repository> map = repos.stream().collect(Collectors.toMap(
Repository::name,
Function.identity(),
(existing, replacement) -> existing,
LinkedHashMap::new
));The fourth parameter is a Supplier that provides the Map implementation. LinkedHashMap maintains a doubly-linked list of entries, preserving the iteration order.
List to Map keeping key ordering
To sort keys alphabetically, use a TreeMap which implements SortedMap.
The following snippet shows how to sort keys:
TreeMap<String, Repository> sorted = repos.stream().collect(Collectors.toMap(
Repository::name,
Function.identity(),
(existing, replacement) -> existing,
TreeMap::new
));For reverse alphabetical order, provide a Comparator to the TreeMap constructor:
repos.stream().collect(Collectors.toMap(
Repository::name,
Function.identity(),
(existing, replacement) -> existing,
() -> new TreeMap<>(Comparator.reverseOrder())
));Creating immutable maps
Since Java 10, you can use Collectors.toUnmodifiableMap() to create an immutable map directly.
The following snippet shows how to create an immutable map:
Map<String, Repository> immutableMap = repos.stream()
.collect(Collectors.toUnmodifiableMap(Repository::name, Function.identity()));Any attempt to modify this map will throw an UnsupportedOperationException. This is useful for creating read-only data structures and ensuring thread safety.
Tip
There's also a version with a merge function for handling duplicate keys: toUnmodifiableMap(keyMapper, valueMapper, mergeFunction).
Handling null values
Warning
Collectors.toMap() throws a NullPointerException when the value mapper returns null. This is because it internally uses HashMap.merge() which doesn't accept null values.
// This throws NullPointerException if description is null!
repos.stream().collect(Collectors.toMap(Repository::name, Repository::description));There are several workarounds (see ListToMapNullHandling.java for full examples):
Option 1: Use a traditional loop with HashMap.put()
Map<String, String> map = new HashMap<>();
repos.forEach(repo -> map.put(repo.name(), repo.description()));Option 2: Replace nulls with a default value
repos.stream().collect(Collectors.toMap(
Repository::name,
repo -> repo.description() != null ? repo.description() : "No description"
));Option 3: Filter out null values before collecting
repos.stream()
.filter(repo -> repo.description() != null)
.collect(Collectors.toMap(Repository::name, Repository::description));When to use toMap() vs groupingBy()
Use Collectors.toMap() when:
- Each element maps to exactly one key-value pair
- Keys should be unique (or you'll provide a merge function)
Use Collectors.groupingBy() when:
- Multiple elements share the same key
- You want a
Map<K, List<V>>structure
// groupingBy: groups multiple repositories by the fork flag
Map<Boolean, List<Repository>> byFork = repos.stream()
.collect(Collectors.groupingBy(Repository::fork));Conclusion
In this post, I've shown how to convert a List to a Map using Java Streams with Collectors.toMap(). Key takeaways:
- Use the basic two-argument
toMap()for unique keys - Add a merge function to handle duplicates
- Use
LinkedHashMapto preserve insertion order - Use
TreeMapfor sorted keys - Use
toUnmodifiableMap()for immutable maps (Java 10+) - Be aware of
NullPointerExceptionwith null values
You can find the full source code for this article on GitHub.

