Java 8 - How to Sort a Map by Key or Value

Introduction

This quick tutorial shows how to sort a Map by key or by value in Java 8. A Map has no sort() method, and a HashMap has no order at all, so "sorting a map" really means producing a new, ordered view of its entries. We will start with the three steps every solution follows. We will then run an example that sorts by key with a TreeMap and with a stream, sorts by value ascending and descending, breaks ties, picks the top three, and shows the common mistake that loses the order again. Lastly, we will summarise which approach to use when.

The three steps

Sorting a map by value always comes down to the same three steps: turn the entries into a stream, sort the stream with a comparator on entries, and collect the result into something that keeps order - a LinkedHashMap or a List.
How to sort a map by value in Java 8 - stream the entries, sort with comparingByValue, collect into a LinkedHashMap

Comparators for map entries

Java 8 added four static methods to Map.Entry that return ready-made ComparatorsClick to read: Comparators -
  • Map.Entry.comparingByKey() and Map.Entry.comparingByValue(): compare entries by the natural order of their keys or values (which must be Comparable).
  • Map.Entry.comparingByKey(Comparator) and Map.Entry.comparingByValue(Comparator): compare using the given comparator - for example Comparator.reverseOrder() for descending order.
Like any comparator, they can be chained with reversed() and thenComparing().

Java 8 code example of sorting a map

The map holds the number of leave days each employee has taken this year. It is a HashMap, so it starts in hash order.
Java 8 code showing how to sort a Map by key and by value
//SortMapExamples.java
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

public class SortMapExamples {

  public static void main(String[] args) {
    Map<String, Integer> leaveTaken = new HashMap<>();
    leaveTaken.put("Tom Jones", 12);
    leaveTaken.put("Harry Major", 5);
    leaveTaken.put("Ethan Hardy", 12);
    leaveTaken.put("Nancy Smith", 8);
    leaveTaken.put("Deborah Sprightly", 3);

    // 1. By key: copy into a TreeMap
    Map<String, Integer> byName = new TreeMap<>(leaveTaken);
    System.out.println("1. by key (TreeMap):        " + byName);

    // 2. By key with a stream, into a LinkedHashMap that keeps the order
    Map<String, Integer> byNameDesc = leaveTaken.entrySet().stream()
        .sorted(Map.Entry.comparingByKey(Comparator.reverseOrder()))
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
            (a, b) -> a, LinkedHashMap::new));
    System.out.println("2. by key, descending:      " + byNameDesc);

    // 3. By value, ascending
    Map<String, Integer> byDays = leaveTaken.entrySet().stream()
        .sorted(Map.Entry.comparingByValue())
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
            (a, b) -> a, LinkedHashMap::new));
    System.out.println("3. by value:                " + byDays);

    // 4. By value descending, ties broken by key
    Comparator<Map.Entry<String, Integer>> mostLeaveFirst =
        Map.Entry.<String, Integer>comparingByValue().reversed()
            .thenComparing(Map.Entry.comparingByKey());
    Map<String, Integer> ranked = leaveTaken.entrySet().stream()
        .sorted(mostLeaveFirst)
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
            (a, b) -> a, LinkedHashMap::new));
    System.out.println("4. by value desc, then key: " + ranked);

    // 5. The top 3 names only
    List<String> topThree = leaveTaken.entrySet().stream()
        .sorted(mostLeaveFirst)
        .limit(3)
        .map(Map.Entry::getKey)
        .collect(Collectors.toList());
    System.out.println("5. top 3: " + topThree);

    // 6. Collecting sorted entries into a HashMap loses the order again
    Map<String, Integer> lost = leaveTaken.entrySet().stream()
        .sorted(Map.Entry.comparingByValue())
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
    System.out.println("6. sorted, then into HashMap: " + lost);

    // 7. Without streams: sort a list of entries
    List<Map.Entry<String, Integer>> entries = new ArrayList<>(leaveTaken.entrySet());
    entries.sort(Collections.reverseOrder(Map.Entry.comparingByValue()));
    System.out.println("7. list of entries sorted: " + entries);
  }
}
 OUTPUT of the above code
1. by key (TreeMap):        {Deborah Sprightly=3, Ethan Hardy=12, Harry Major=5, Nancy Smith=8, Tom Jones=12}
2. by key, descending:      {Tom Jones=12, Nancy Smith=8, Harry Major=5, Ethan Hardy=12, Deborah Sprightly=3}
3. by value:                {Deborah Sprightly=3, Harry Major=5, Nancy Smith=8, Ethan Hardy=12, Tom Jones=12}
4. by value desc, then key: {Ethan Hardy=12, Tom Jones=12, Nancy Smith=8, Harry Major=5, Deborah Sprightly=3}
5. top 3: [Ethan Hardy, Tom Jones, Nancy Smith]
6. sorted, then into HashMap: {Deborah Sprightly=3, Ethan Hardy=12, Harry Major=5, Nancy Smith=8, Tom Jones=12}
7. list of entries sorted: [Ethan Hardy=12, Tom Jones=12, Nancy Smith=8, Harry Major=5, Deborah Sprightly=3]

Explanation of the code

  • Part 1: copying the map into a TreeMap is the simplest way to sort by key. The TreeMap stays sorted as entries are added later.
  • Part 2: to sort by key in a custom order, stream the entries, sort them with comparingByKey(Comparator.reverseOrder()), and collect with the four-argument toMap()Click to read tutorial: Java 8 Collectors.toMap Tutorial with Examples into a LinkedHashMap, which remembers insertion order.
  • Part 3: the same pipeline with comparingByValue() sorts by value. Ethan and Tom both have 12 days; the sort is stable, so they keep the order in which the stream met them.
  • Part 4: comparingByValue().reversed() puts the most leave first, and thenComparing(comparingByKey()) orders ties by name. The type witness Map.Entry.<String, Integer>comparingByValue() is needed because Java cannot infer the types when reversed() is called on the result straight away.
  • Part 5: when only the top entries are needed, limit(3) and map(Map.Entry::getKey) give a plain list of names.
  • Part 6: the two-argument toMap() collects into a HashMap, which orders entries by hash code, not by the order they arrive in. The sorting in the pipeline is wasted. (For these names, hash order happens to look alphabetical - it is still not the value order of part 3.)
  • Part 7: without streams, copy entrySet() into a list and call List.sort()Click to read: List.sort() with the same comparators. Collections.reverseOrder(comparator) reverses any comparator.

Which approach to use

  • Sorted by key, and kept sorted: use a TreeMap, optionally with a comparator: new TreeMap<>(Comparator.reverseOrder()).
  • Sorted by value: there is no map that keeps itself sorted by value, because values can change and repeat. Sort the entries when you need the order, as in parts 3 and 4.
  • Need a map afterwards: collect into a LinkedHashMap. Need a ranking or top N: collect into a List.
  • Ties: always add a thenComparing() on the key if the order of equal values matters, so the result does not depend on the hash order of the original map.

Summary

In this tutorial we first saw that sorting a map means streaming its entries, sorting them, and collecting into something that keeps order. Next we covered the comparators Java 8 added to Map.Entry. We then ran an example that sorted by key with a TreeMap and with a stream, sorted by value ascending and descending with ties broken by key, took the top three, lost the order by collecting into a HashMap, and sorted a list of entries without streams. Lastly, we summarised which approach fits which need.

Java 8 - How to Sort a Map by Key or Value - JavaBrahman