Saturday, July 29, 2017

How to sort a Map by keys in Java 8 - Example Tutorial

In the last article, I have shown you how to sort a Map by values in Java 8 and in this tutorial, you will learn how to sort a Map by keys e.g. an HashMap, ConcurrentHashMap, LinkedHashmap, or even Hashtable. Theoretically, you cannot sort a Map because it doesn't provide any ordering guarantee. For example, when you iterate over a HashMap, you don't know in which order entries will be traversed because HashMap doesn't provide any ordering. Then, how can you sort a Map which doesn't support order? Well, you can't and that's why you only sort entries of HashMap but you don't store the result back into HasMap or any other Map which doesn't support ordering. If you do so, then sorting will be lost.

Here is an example of incorrect sorting. Here even after sorting the Map, we are doing the mistake of storing the result back into a Map which doesn't provide any ordering guarantee, hence the result is an unordered map even after sorting.

Map sorted = budget
.collect(toMap(e -> e.getKey(), e -> e.getValue(), (e1, e2) -> e2));

Here is the output to confirm what I said:
map before sorting: {grocery=150, utility=130, miscellneous=90,
 rent=1150, clothes=120, transportation=100}
map after sorting by keys: {grocery=150, utility=130, miscellneous=90,
 rent=1150, clothes=120, transportation=100}

If Map was sorted then the "clothes" should have come first ahead of "grocery". The mistake was blindly relying on toMap() method of Collectors class. This class provides no guarantee of what kind of Map will be used to collect those elements. Since Map interface doesn't guarantee order, they are also not bound to store element in any order.

Though, it's easy to solve this problem because Collectors class also provide an overloaded version of toMap() class which allows you to instruct which kind of Map should be used to store those entries. You can use a LinkedHashMap to store mappings to preserve the sorting order because LinkedHashMap keep keys in the order they were added. Here is the modified code which sorts a Map in the order of keys:

Map sorted = budget
.collect(toMap(e -> e.getKey(), e -> e.getValue(),
(e1, e2) -> e2), LinkedHashMap::new));

The code passed into to toMap() method is interesting, the first parameter is used as a key, second is used as value and third is used to break ties i.e. if two entries are equal then which entries will be chosen is decided by the third parameter, here we are using the second entry. The fourth parameter is the important one, which uses a constructor reference to tell Collector that for copying a LinkedHashMap should be used.  See Java SE 8 for the Really Impatient to learn more about how constructor interference is used.

Steps to sort a Map by keys in Java 8

Here are the high-level steps you can take to sort a Map e.g. HashMap, Hashtable, ConcurentHashMap or LinkedHashMap to sort them in the ascending and descending order of their keys:

1) Get all entries by calling the Map.entrySet() method

2) Get a stream of entries by calling the stream() method, which Set inherit from Collection interface.

3) Sort all entries of Stream by calling the sorted() method.

4) In order to sort them by keys, provide a Comparator to a sorted() method which sorts entries by keys. This can be done by calling Map.Entry.comparingKey() method returns a Comparator which compares key in their natural order.

5) Store the result of sorting in a LinkedHashMap by using the collect() method of Stream class.

6) Use Collectors.toMap() method to collect sorted entries into LinkedHashMap

Java Program to sort a Map by keys in JDK 8

Here is the complete Java program to sort Map e.g. HashMap by keys in JDK 8. In this example, you will learn to sort Map by both lambda expression and method reference. We'll also use new classes e.g. Stream and new methods added into Map.Entry class to sort all entries by their Map and store the result into a LinkedHashMap.

import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import static*;
import static java.util.Map.Entry.*;

* Java Program to sort a Map by keys in Java 8
public class Java8Demo{

public static void main(String[] args) throws Exception {

// a Map with string keys and integer values
Map<String, Integer> budget = new HashMap<>();
budget.put("clothes", 120);
budget.put("grocery", 150);
budget.put("transportation", 100);
budget.put("utility", 130);
budget.put("rent", 1150);
budget.put("miscellneous", 90);

System.out.println("map before sorting: " + budget);

// let's sort this map by keys first
Map<String, Integer> sorted = budget
toMap(e -> e.getKey(), e -> e.getValue(),
(e1, e2) -> e2, LinkedHashMap::new));

System.out.println("map after sorting by keys: " + sorted);

// above code can be cleaned a bit by using method reference
sorted = budget
toMap(Map.Entry::getKey, Map.Entry::getValue,
(e1, e2) -> e2, LinkedHashMap::new));

// now let's sort the map in decreasing order of keys
sorted = budget
toMap(Map.Entry::getKey, Map.Entry::getValue,
(e1, e2) -> e2, LinkedHashMap::new));

System.out.println("map after sorting by keys in descending order: " + sorted);


map before sorting: {grocery=150, utility=130, miscellneous=90, 
       rent=1150, clothes=120, transportation=100}
map after sorting by keys: {clothes=120, grocery=150, miscellneous=90, 
       rent=1150, transportation=100, utility=130}
map after sorting by keys in descending order: {utility=130, 
       transportation=100, rent=1150, miscellneous=90, grocery=150, clothes=120}

You can see that initially map was not sorted but it is later sorted in the order of keys, which are a string and that's why clothes come ahead of grocery. Similarly, when we sorted the map in the descending order, clothes come last. This proves that our sorting code is working fine.

If you want more sophistication and customization you can do that at Comparator level and you can provide additional Comparator to comparingKey() method, which by default compare keys in their natural order.

For example, if a key were not String but a user object e.g. a Book, then you could have sorted book by title, author or price by providing the corresponding comparator to comparingKey() method of java.util.Map.Entry class. Both comparingKey() and comparingValue() are overloaded to accept a Comparator. You can see a good Java 8 book e.g. Java SE 8 for Really Impatient to learn more about them.

How to sort a Map by keys in Java 8

That's all about how to sort a Map by keys in Java 8. The simplest way to achieve this is by using the sorted() method of Stream and the newly added comparingKey() method of Map.Entry class. The stream sorts all elements and then depending upon your need, you can either print entries in sorted order or stored them in an ordered map e.g. LinkedHashMap or a sorted map e.g. TreeMap. You can also sort entries in their reverse order by just reversing the Comparator using the Collections.reverseOrder() method or Comparator.reversed() method of Java 8.

Further Learning
The Complete Java MasterClass
Java SE 8 Developer BootCamp
Refactoring to Java 8 Streams and Lambdas Self- Study Workshop

Related Java 8 Tutorials
If you are interested in learning more about new features of Java 8, here are my earlier articles covering some of the important concepts of Java 8:

  • 20 Examples of Date and Time in Java 8 (tutorial)
  • How to use Stream class in Java 8 (tutorial)
  • How to use filter() method in Java 8 (tutorial)
  • How to use forEach() method in Java 8 (example)
  • How to join String in Java 8 (example)
  • How to convert List to Map in Java 8 (solution)
  • How to use peek() method in Java 8 (example)
  • 5 Books to Learn Java 8 from Scratch (books)
Thank for reading this article so far. If you like this tutorial then please share with your friends and colleagues. If you have any question or feedback then please drop a comment.

P.S. : If you want to learn more about new features in Java 8 then please see the tutorial What's New in Java 8. It explains about all important features of Java 8 e.g. lambda expressions, streams, functional inteface, Optionals, new date and time API and other miscelleneous changes.



Well would like to go for short approach ,use a TreeMap. This is precisely what its for. If this map is passed to you and you cannot determine the type, then you can do the following:

SortedSet keys = new TreeSet(map.keySet());
for (String key : keys) {
String value = map.get(key);


asdf said...

It's a great simple example but it's impossible to port it to something useful.

Javin Paul said...

Hello asdf, thanks, but what do you mean by port to something useful? Can you please elaborate?

Post a Comment