Java Collectors.toMap(): Collecting Stream Items into Map

Lokesh Gupta

Learn to convert a Stream to Map i.e. collect the items from a Stream into Map using Collectors.toMap() and Collectors.groupingBy() methods.

1. Watch out for IllegalStateException

Please note that it is very important to know beforehand if the Stream elements will have a distinct value for the map key field or not. If map keys are duplicates and we use Collectors.toMap() method, we will get the IllegalStateException:

Exception in thread "main" java.lang.IllegalStateException: 
	Duplicate key 3 (attempted merging values Item[id=3, name=Item3] and Item[id=3, name=Item3])
	at java.base/java.util.stream.Collectors.duplicateKeyException(Collectors.java:135)

2. Convert Stream to Map (without Duplicate Keys)

In this case, we must make sure that each key is unique in the Stream. After the process finishes, we will get a Map<K,V> where a specified key K is associated with only one single value V.

For the demo purpose, we are using a Stream of 3 items:

Stream<Item> stream = Stream.of(

  new Item(1, "Item1"),
  new Item(2, "Item2"),
  new Item(3, "Item3")
);

We can convert the above Strem of Item instance into Map in two ways. The Map key will always be the Item Id but the Map value can be either the item value of the item itself.

In the following example, we are converting into Map<Long, String> such that:

  • Map Key – Item Id
  • Map Value – Item Name
Map<Long, String> mapWithValue = stream.collect(Collectors.toMap(Item::id, Item::name));

// {1=Item1, 2=Item2, 3=Item3}

In the next example, we are converting to Map<Long, Item> using the Function.identity() that returns the object itself as Map value.

Map<Long, Item> map = stream.collect(Collectors.toMap(Item::id, Function.identity()));

// {1=Item[id=1, name=Item1], 2=Item[id=2, name=Item2], 3=Item[id=3, name=Item3]}

3. Convert Stream to Map (with Duplicate Keys)

If the stream has items where Map keys are duplicates, there are two possible ways to handle it:

  • Collect all values in List and associate them with the Key
  • Choose only one value, and discard all other values for a Key

For the demo purpose, we are using a Stream of five items where three items have the duplicate key ‘3‘.

Stream<Item> streamWithDuplicates = Stream.of(

  new Item(1, "Item1"),
  new Item(2, "Item2"),
  new Item(3, "Item3-1"),
  new Item(3, "Item3-2"),
  new Item(3, "Item3-3")
);

3.1. Collecting Stream to Map of Lists

In case, we want to store all values for a key in a List, we can use Collectors.groupingBy() to collect elements in Map<key, List<value>> format.

In the following example, we are collecting the items into Map of Lists i.e. Map<K, List<Item>>.

Map<Long, List<Item>> mapWithList = streamWithDuplicates.collect(Collectors.groupingBy(Item::id));

The program output:

{
	1=[Item[id=1, name=Item1]], 
	2=[Item[id=2, name=Item2]], 
	3=[
			Item[id=3, name=Item3-1], 
			Item[id=3, name=Item3-2], 
			Item[id=3, name=Item3-3]
		]
}

We can convert the Stream to such that List contains the Item names i.e. Map<K, List<String>>.

Map<Long, List<String>> mapWithGroupedValues = streamWithDuplicates
    .collect(
        Collectors.groupingBy(Item::id, 
        Collectors.mapping(Item::name, Collectors.toList())));

The program output:

{
	1=[Item1], 
	2=[Item2], 
	3=[Item3-1, Item3-2, Item3-3]
}

3.2. Collecting Stream to Map with Discarding Duplicate Values

Another way is to to choose only one value from multiple values, and discard other values for any Key.

In our example, we may decide to choose the most recent name for the Map key i.e. item3-3 and discard the other values i.e. item3-1 and item3-2.

The following code uses the third parameter, mergeFunction, which when encountering conflicting values on a key, chooses which value must be associated with the key.

Map<Long, Item> mapWithGrouping = streamWithDuplicates
    .collect(Collectors.toMap(Item::id, Function.identity(), (oldValue, newValue) -> newValue));

The program output:

{
	1=Item[id=1, name=Item1], 
	2=Item[id=2, name=Item2], 
	3=Item[id=3, name=Item3-3]
}

4. Maintaining Insertion Order or Sorting of keys

At times, we may want to maintain the order of key-value pairs in which they are inserted into the Map. The LinkedHashMap maintains such insertion order so we can use it to collect the Stream items.

LinkedHashMap<Long, String> mapWithValueInInsertionOrder = stream
    .collect(Collectors.toMap(
    	Item::id, 
    	Item::name, 
    	(o, n) -> n, 
    	LinkedHashMap::new));

Similarly, if we want to apply and maintain the sorting order in the Map keys, we can use the TreeMap.

TreeMap<Long, String> mapwithSortedKeys = stream
    .collect(Collectors.toMap(
    	Item::id, 
    	Item::name, 
    	(o, n) -> n, 
    	TreeMap::new));

5. Conclusion

This Java Stream tutorial discussed various ways to collect the stream items into a Map, or a Map of Lists. We saw the examples of each approach and their outputs for a better understanding of what each approach does to Stream items.

Happy Learning !!

Source Code on Github

Comments

Subscribe
Notify of
guest
2 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.

Our Blogs

REST API Tutorial

Dark Mode

Dark Mode