BLOG POSTS
Sort HashMap by Value in Java – Example Methods

Sort HashMap by Value in Java – Example Methods

Sorting a HashMap by its values is a common task in Java programming that often stumps developers who expect key-based operations. Unlike TreeMap which maintains natural ordering by keys, HashMap stores entries in an unpredictable order, making value-based sorting a multi-step process. This guide walks through several proven methods to sort HashMap entries by their values, covering both ascending and descending order scenarios, performance considerations, and real-world applications you’ll encounter in production systems.

How HashMap Value Sorting Works

HashMap sorting by value requires converting the key-value pairs into a sortable structure since HashMap itself doesn’t maintain any ordering. The process typically involves extracting entries, applying a comparator based on values, and collecting results into a new data structure.

The fundamental challenge stems from HashMap’s internal structure – it uses hash buckets for O(1) access time but sacrifices ordering. When you need value-based sorting, you’re essentially trading that fast access for ordered results.

Method 1: Using Streams and Collectors (Java 8+)

The most elegant approach leverages Java 8’s Stream API with custom collectors:

import java.util.*;
import java.util.stream.Collectors;

public class HashMapSorting {
    public static void main(String[] args) {
        HashMap<String, Integer> originalMap = new HashMap<>();
        originalMap.put("apple", 50);
        originalMap.put("banana", 20);
        originalMap.put("cherry", 80);
        originalMap.put("date", 10);
        
        // Sort by value in ascending order
        LinkedHashMap<String, Integer> sortedAsc = originalMap.entrySet()
            .stream()
            .sorted(Map.Entry.comparingByValue())
            .collect(Collectors.toMap(
                Map.Entry::getKey,
                Map.Entry::getValue,
                (e1, e2) -> e1,
                LinkedHashMap::new
            ));
            
        // Sort by value in descending order
        LinkedHashMap<String, Integer> sortedDesc = originalMap.entrySet()
            .stream()
            .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
            .collect(Collectors.toMap(
                Map.Entry::getKey,
                Map.Entry::getValue,
                (e1, e2) -> e1,
                LinkedHashMap::new
            ));
            
        System.out.println("Original: " + originalMap);
        System.out.println("Ascending: " + sortedAsc);
        System.out.println("Descending: " + sortedDesc);
    }
}

This approach creates a new LinkedHashMap that preserves insertion order, which becomes the sorted order after processing. The key insight is using `LinkedHashMap::new` as the map supplier to maintain ordering.

Method 2: Traditional List-Based Sorting

For environments where Java 8+ features aren’t available or when you need more control over the sorting process:

import java.util.*;

public class TraditionalSorting {
    public static LinkedHashMap<String, Integer> sortByValue(HashMap<String, Integer> map, boolean ascending) {
        List<Map.Entry<String, Integer>> entries = new ArrayList<>(map.entrySet());
        
        Collections.sort(entries, new Comparator<Map.Entry<String, Integer>>() {
            @Override
            public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
                if (ascending) {
                    return o1.getValue().compareTo(o2.getValue());
                } else {
                    return o2.getValue().compareTo(o1.getValue());
                }
            }
        });
        
        LinkedHashMap<String, Integer> result = new LinkedHashMap<>();
        for (Map.Entry<String, Integer> entry : entries) {
            result.put(entry.getKey(), entry.getValue());
        }
        
        return result;
    }
    
    public static void main(String[] args) {
        HashMap<String, Integer> scores = new HashMap<>();
        scores.put("Alice", 95);
        scores.put("Bob", 87);
        scores.put("Charlie", 92);
        scores.put("Diana", 98);
        
        LinkedHashMap<String, Integer> topScores = sortByValue(scores, false);
        System.out.println("Leaderboard: " + topScores);
    }
}

Method 3: Custom Comparator with Generic Support

A reusable utility method that handles different value types:

import java.util.*;
import java.util.stream.Collectors;

public class GenericHashMapSorter {
    
    public static <K, V extends Comparable<? super V>> LinkedHashMap<K, V> 
        sortByValue(Map<K, V> map, boolean ascending) {
        
        Comparator<Map.Entry<K, V>> comparator = ascending ? 
            Map.Entry.comparingByValue() : 
            Map.Entry.<K, V>comparingByValue().reversed();
            
        return map.entrySet()
            .stream()
            .sorted(comparator)
            .collect(Collectors.toMap(
                Map.Entry::getKey,
                Map.Entry::getValue,
                (e1, e2) -> e1,
                LinkedHashMap::new
            ));
    }
    
    public static void main(String[] args) {
        // Example with Double values
        HashMap<String, Double> temperatures = new HashMap<>();
        temperatures.put("New York", 15.5);
        temperatures.put("Miami", 28.3);
        temperatures.put("Denver", 8.1);
        temperatures.put("Seattle", 12.7);
        
        LinkedHashMap<String, Double> coldestFirst = sortByValue(temperatures, true);
        System.out.println("Coldest to warmest: " + coldestFirst);
        
        // Example with String values
        HashMap<String, String> countries = new HashMap<>();
        countries.put("US", "United States");
        countries.put("UK", "United Kingdom");
        countries.put("DE", "Germany");
        countries.put("FR", "France");
        
        LinkedHashMap<String, String> alphabetical = sortByValue(countries, true);
        System.out.println("Alphabetical by name: " + alphabetical);
    }
}

Performance Comparison and Benchmarks

Method Time Complexity Space Complexity Best For Java Version
Stream API O(n log n) O(n) Readable, functional style 8+
Collections.sort() O(n log n) O(n) Older Java versions 1.2+
TreeMap conversion O(n log n) O(n) Maintaining sorted state 1.2+
Priority Queue O(n log k) O(k) Top-K scenarios 1.5+

Here’s a benchmark test showing performance differences:

import java.util.*;
import java.util.stream.Collectors;

public class SortingBenchmark {
    
    private static final int MAP_SIZE = 100000;
    
    public static void main(String[] args) {
        HashMap<String, Integer> testMap = generateTestData(MAP_SIZE);
        
        // Warm up JVM
        for (int i = 0; i < 5; i++) {
            sortWithStreams(new HashMap<>(testMap));
            sortWithCollections(new HashMap<>(testMap));
        }
        
        // Benchmark Stream API
        long startTime = System.nanoTime();
        for (int i = 0; i < 10; i++) {
            sortWithStreams(new HashMap<>(testMap));
        }
        long streamTime = (System.nanoTime() - startTime) / 10;
        
        // Benchmark Collections.sort
        startTime = System.nanoTime();
        for (int i = 0; i < 10; i++) {
            sortWithCollections(new HashMap<>(testMap));
        }
        long collectionsTime = (System.nanoTime() - startTime) / 10;
        
        System.out.printf("Stream API: %d ns%n", streamTime);
        System.out.printf("Collections.sort: %d ns%n", collectionsTime);
        System.out.printf("Performance ratio: %.2f%n", (double) streamTime / collectionsTime);
    }
    
    private static HashMap<String, Integer> generateTestData(int size) {
        HashMap<String, Integer> map = new HashMap<>();
        Random random = new Random(42); // Fixed seed for consistent results
        
        for (int i = 0; i < size; i++) {
            map.put("key" + i, random.nextInt(10000));
        }
        return map;
    }
    
    private static LinkedHashMap<String, Integer> sortWithStreams(HashMap<String, Integer> map) {
        return map.entrySet().stream()
            .sorted(Map.Entry.comparingByValue())
            .collect(Collectors.toMap(
                Map.Entry::getKey,
                Map.Entry::getValue,
                (e1, e2) -> e1,
                LinkedHashMap::new
            ));
    }
    
    private static LinkedHashMap<String, Integer> sortWithCollections(HashMap<String, Integer> map) {
        List<Map.Entry<String, Integer>> entries = new ArrayList<>(map.entrySet());
        Collections.sort(entries, Map.Entry.comparingByValue());
        
        LinkedHashMap<String, Integer> result = new LinkedHashMap<>();
        for (Map.Entry<String, Integer> entry : entries) {
            result.put(entry.getKey(), entry.getValue());
        }
        return result;
    }
}

Real-World Use Cases and Examples

Web Server Request Monitoring

When running applications on VPS services, monitoring request frequencies becomes crucial:

import java.util.*;
import java.util.stream.Collectors;

public class RequestMonitor {
    private HashMap<String, Integer> requestCounts = new HashMap<>();
    
    public void logRequest(String endpoint) {
        requestCounts.merge(endpoint, 1, Integer::sum);
    }
    
    public LinkedHashMap<String, Integer> getTopEndpoints(int limit) {
        return requestCounts.entrySet()
            .stream()
            .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
            .limit(limit)
            .collect(Collectors.toMap(
                Map.Entry::getKey,
                Map.Entry::getValue,
                (e1, e2) -> e1,
                LinkedHashMap::new
            ));
    }
    
    public static void main(String[] args) {
        RequestMonitor monitor = new RequestMonitor();
        
        // Simulate request logging
        String[] endpoints = {"/api/users", "/api/orders", "/api/products", "/health", "/api/users"};
        int[] frequencies = {150, 89, 234, 12, 175};
        
        for (int i = 0; i < endpoints.length; i++) {
            for (int j = 0; j < frequencies[i]; j++) {
                monitor.logRequest(endpoints[i]);
            }
        }
        
        System.out.println("Top 3 endpoints:");
        monitor.getTopEndpoints(3).forEach((endpoint, count) -> 
            System.out.printf("%s: %d requests%n", endpoint, count));
    }
}

Database Query Performance Analysis

Perfect for analyzing slow queries on dedicated servers:

public class QueryPerformanceAnalyzer {
    private HashMap<String, Double> queryTimes = new HashMap<>();
    
    public void recordQuery(String query, double executionTimeMs) {
        queryTimes.put(query, executionTimeMs);
    }
    
    public void printSlowestQueries() {
        LinkedHashMap<String, Double> slowestFirst = queryTimes.entrySet()
            .stream()
            .sorted(Map.Entry.<String, Double>comparingByValue().reversed())
            .collect(Collectors.toMap(
                Map.Entry::getKey,
                Map.Entry::getValue,
                (e1, e2) -> e1,
                LinkedHashMap::new
            ));
            
        System.out.println("Queries needing optimization:");
        slowestFirst.entrySet().stream()
            .filter(entry -> entry.getValue() > 100.0) // Queries over 100ms
            .forEach(entry -> 
                System.out.printf("%.2fms: %s%n", entry.getValue(), 
                    entry.getKey().length() > 50 ? 
                    entry.getKey().substring(0, 50) + "..." : entry.getKey()));
    }
}

Alternative Approaches and When to Use Them

TreeMap for Maintained Sorting

When you need persistent sorting throughout the application lifecycle:

import java.util.*;

public class MaintainedSorting {
    // Custom comparator that sorts by value
    private static class ValueComparator implements Comparator<String> {
        private final Map<String, Integer> base;
        
        public ValueComparator(Map<String, Integer> base) {
            this.base = base;
        }
        
        @Override
        public int compare(String a, String b) {
            int valueComparison = base.get(a).compareTo(base.get(b));
            return valueComparison != 0 ? valueComparison : a.compareTo(b);
        }
    }
    
    public static TreeMap<String, Integer> createSortedByValue(Map<String, Integer> original) {
        ValueComparator comparator = new ValueComparator(original);
        TreeMap<String, Integer> sorted = new TreeMap<>(comparator);
        sorted.putAll(original);
        return sorted;
    }
}

Priority Queue for Top-K Problems

When you only need the top N elements, Priority Queue offers better performance:

import java.util.*;

public class TopKElements {
    public static <K, V extends Comparable<V>> List<Map.Entry<K, V>> 
        getTopK(Map<K, V> map, int k) {
        
        PriorityQueue<Map.Entry<K, V>> minHeap = new PriorityQueue<>(
            k, Map.Entry.comparingByValue()
        );
        
        for (Map.Entry<K, V> entry : map.entrySet()) {
            if (minHeap.size() < k) {
                minHeap.offer(entry);
            } else if (entry.getValue().compareTo(minHeap.peek().getValue()) > 0) {
                minHeap.poll();
                minHeap.offer(entry);
            }
        }
        
        List<Map.Entry<K, V>> result = new ArrayList<>(minHeap);
        result.sort(Map.Entry.<K, V>comparingByValue().reversed());
        return result;
    }
}

Common Pitfalls and Best Practices

Memory Considerations

  • Always consider memory overhead when creating sorted copies of large HashMaps
  • For large datasets, consider streaming processing instead of loading everything into memory
  • Use primitive collections (like TIntObjectHashMap from Trove) for better memory efficiency with primitive values
  • Monitor garbage collection impact when frequently sorting large maps

Thread Safety Issues

// Wrong - not thread-safe
public class UnsafeSorting {
    private HashMap<String, Integer> data = new HashMap<>();
    
    public LinkedHashMap<String, Integer> getSortedData() {
        // Race condition: data might be modified during sorting
        return data.entrySet().stream()
            .sorted(Map.Entry.comparingByValue())
            .collect(Collectors.toMap(/*...*/));
    }
}

// Correct - thread-safe approach
public class SafeSorting {
    private final ConcurrentHashMap<String, Integer> data = new ConcurrentHashMap<>();
    
    public LinkedHashMap<String, Integer> getSortedData() {
        // Create snapshot for consistent sorting
        Map<String, Integer> snapshot = new HashMap<>(data);
        return snapshot.entrySet().stream()
            .sorted(Map.Entry.comparingByValue())
            .collect(Collectors.toMap(
                Map.Entry::getKey,
                Map.Entry::getValue,
                (e1, e2) -> e1,
                LinkedHashMap::new
            ));
    }
}

Null Value Handling

public class NullSafeComparator {
    public static <K, V extends Comparable<V>> LinkedHashMap<K, V> 
        sortByValueNullSafe(Map<K, V> map, boolean nullsFirst) {
        
        Comparator<Map.Entry<K, V>> comparator = nullsFirst ?
            Map.Entry.<K, V>comparingByValue(Comparator.nullsFirst(Comparator.naturalOrder())) :
            Map.Entry.<K, V>comparingByValue(Comparator.nullsLast(Comparator.naturalOrder()));
            
        return map.entrySet().stream()
            .sorted(comparator)
            .collect(Collectors.toMap(
                Map.Entry::getKey,
                Map.Entry::getValue,
                (e1, e2) -> e1,
                LinkedHashMap::new
            ));
    }
}

Integration with Popular Frameworks

Spring Boot Integration

Creating a reusable service for HashMap sorting in Spring applications:

import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class MapSortingService {
    
    public <K, V extends Comparable<? super V>> LinkedHashMap<K, V> 
        sortByValue(Map<K, V> map, SortOrder order) {
        
        Comparator<Map.Entry<K, V>> comparator = order == SortOrder.ASC ?
            Map.Entry.comparingByValue() :
            Map.Entry.<K, V>comparingByValue().reversed();
            
        return map.entrySet().stream()
            .sorted(comparator)
            .collect(Collectors.toMap(
                Map.Entry::getKey,
                Map.Entry::getValue,
                (e1, e2) -> e1,
                LinkedHashMap::new
            ));
    }
    
    public enum SortOrder {
        ASC, DESC
    }
}

Performance Monitoring

For production environments, especially on managed infrastructure, monitoring sorting performance becomes crucial:

import java.util.concurrent.atomic.AtomicLong;

public class MonitoredHashMapSorter {
    private final AtomicLong sortOperations = new AtomicLong(0);
    private final AtomicLong totalSortTime = new AtomicLong(0);
    
    public <K, V extends Comparable<? super V>> LinkedHashMap<K, V> 
        sortByValueWithMetrics(Map<K, V> map) {
        
        long startTime = System.nanoTime();
        
        LinkedHashMap<K, V> result = map.entrySet().stream()
            .sorted(Map.Entry.comparingByValue())
            .collect(Collectors.toMap(
                Map.Entry::getKey,
                Map.Entry::getValue,
                (e1, e2) -> e1,
                LinkedHashMap::new
            ));
            
        long endTime = System.nanoTime();
        sortOperations.incrementAndGet();
        totalSortTime.addAndGet(endTime - startTime);
        
        return result;
    }
    
    public double getAverageSortTimeMs() {
        long ops = sortOperations.get();
        return ops == 0 ? 0 : (totalSortTime.get() / ops) / 1_000_000.0;
    }
}

The techniques covered here provide robust solutions for HashMap value sorting across different Java versions and use cases. Whether you’re building monitoring dashboards, analyzing performance metrics, or implementing leaderboard systems, these methods offer the flexibility and performance needed for production applications. For detailed Java Collections documentation, refer to Oracle’s official Collections guide.



This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.

This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.

Leave a reply

Your email address will not be published. Required fields are marked