Find Max or Min Value in a Java HashMap (and Its Key)

To find the max value in a Java HashMap, stream its entrySet() and call max(Map.Entry.comparingByValue()) to get the key and the value together. Learn the min value, ties, empty maps and null values too.

Java Collections

To find the max value in a HashMap or any other Java Map, we stream its entrySet() and call max(Map.Entry.comparingByValue()), which returns an Optional with the entry that has the largest value, so we get the key and the value together. The call min(Map.Entry.comparingByValue()) finds the smallest value in the same way, and Collections.max(map.values()) returns only the value.

We need the max or min value of a map whenever the map holds counts or prices, for example to show the best-selling product from a map of sales per product, or the cheapest offer from a map of prices per shop.

The following example uses a small map of fruit stock and finds the largest and the smallest value with their keys, with the result of each line as a comment.

Map<String, Integer> stock = Map.of("apple", 5, "banana", 3, "cherry", 8);

Optional<Map.Entry<String, Integer>> maxEntry = stock.entrySet().stream()
    .max(Map.Entry.comparingByValue());                         // Optional[cherry=8]
Optional<Map.Entry<String, Integer>> minEntry = stock.entrySet().stream()
    .min(Map.Entry.comparingByValue());                         // Optional[banana=3]

String maxKey = maxEntry.map(Map.Entry::getKey).orElse("none");  // "cherry"
int maxValue = maxEntry.map(Map.Entry::getValue).orElse(0);      // 8
int minValue = Collections.min(stock.values());                  // 3

Notice that the stream version returns an Optional, so an empty map gives Optional.empty instead of an exception, whereas Collections.min() throws a NoSuchElementException for an empty map.

Next, we look at the stream and the Collections approaches in detail and find the min and max in one loop. After that, we handle the cases that break the simple version, such as ties and null values.

1. Using Java Streams

A Map is not a stream and has no max() method, so we stream one of its views. The entrySet() view gives us Map.Entry objects, and each entry holds a key and its value. Java streams have the methods Stream.max() and Stream.min(), which take a Comparator and return the largest or the smallest element as an Optional.

The method Map.Entry.comparingByValue() returns a Comparator that compares two entries by their values in natural order. It works for any value type that implements Comparable, such as Integer, Double, String or LocalDate.

For example, a reporting page shows the product with the most sales of the month. The sales are stored in a Map from product name to quantity, and the page needs the name, not only the number.

Optional<Map.Entry<String, Integer>> maxEntry = stock.entrySet().stream()
    .max(Map.Entry.comparingByValue());                         // Optional[cherry=8]

String maxKey = maxEntry.map(Map.Entry::getKey).orElse("none");  // "cherry"
int maxValue = maxEntry.map(Map.Entry::getValue).orElse(0);      // 8

String minKey = stock.entrySet().stream()
    .min(Map.Entry.comparingByValue())
    .map(Map.Entry::getKey)
    .orElse("none");                                            // "banana"

When we need only the number and not the key, we stream values() instead. The method mapToInt() turns the stream into an IntStream, whose max() returns an OptionalInt.

Optional<Integer> maxStock = stock.values().stream()
    .max(Comparator.naturalOrder());                            // Optional[8]
int maxOrZero = stock.values().stream()
    .mapToInt(Integer::intValue)
    .max()
    .orElse(0);                                                 // 8

We never call get() on the result without a check, because Optional.get() throws a NoSuchElementException when the map is empty. The calls map() and orElse() give a default value instead, so an empty map returns “none”.

Map<String, Integer> empty = new HashMap<>();

String noKey = empty.entrySet().stream()
    .max(Map.Entry.comparingByValue())
    .map(Map.Entry::getKey)
    .orElse("none");                                            // "none"

2. Using Collections.max() and Collections.min()

The methods Collections.max() and Collections.min() go through a collection once and return its largest or smallest element. Without a Comparator, they compare the elements by natural order, so the elements must be Comparable. With a Comparator, they use it instead.

We pass values() to get the max value, or entrySet() together with Map.Entry.comparingByValue() to get the whole entry with its key.

int maxValue = Collections.max(stock.values());                 // 8
int minValue = Collections.min(stock.values());                 // 3

Map.Entry<String, Integer> maxEntry =
    Collections.max(stock.entrySet(), Map.Entry.comparingByValue());   // cherry=8
Map.Entry<String, Integer> minEntry =
    Collections.min(stock.entrySet(), Map.Entry.comparingByValue());   // banana=3

The code is shorter than the stream version because there is no Optional, but it has no way to return an empty result. For an empty map, Collections.max() and Collections.min() throw a NoSuchElementException. So we check isEmpty() first when the map can be empty, for example when it comes from a database query.

Map<String, Integer> empty = new HashMap<>();

int failed = Collections.max(empty.values());                   // NoSuchElementException
int safeMax = empty.isEmpty() ? 0 : Collections.max(empty.values());   // 0

3. Using Iteration

A plain for loop over entrySet() finds the max and the min entries in a single pass over the map. The loop keeps the best entry so far in a variable and replaces it whenever the current entry has a larger value. It needs two separate if checks, one for the max and one for the min, because the first entry is both the max and the min at the start.

Map.Entry<String, Integer> maxEntry = null;
Map.Entry<String, Integer> minEntry = null;

for (Map.Entry<String, Integer> entry : stock.entrySet()) {
  if (maxEntry == null || entry.getValue() > maxEntry.getValue()) {
    maxEntry = entry;
  }
  if (minEntry == null || entry.getValue() < minEntry.getValue()) {
    minEntry = entry;
  }
}
// maxEntry = cherry=8, minEntry = banana=3

Notice that both variables stay null for an empty map, so we check them for null before calling getValue(). A version with else instead of the second if is a common bug. With else, an entry becomes the min only when it is not a new max, so for values that arrive in ascending order, such as 3, 5 and 8, the min stays null.

3.1. Max, Min and Sum With IntSummaryStatistics

When the values are numbers and we need several results, such as the max, the min, the sum and the average for a report, IntSummaryStatistics collects all of them in one pass. The class gives only the values, not the keys.

IntSummaryStatistics stats = stock.values().stream()
    .mapToInt(Integer::intValue)
    .summaryStatistics();

int max = stats.getMax();       // 8
int min = stats.getMin();       // 3
long sum = stats.getSum();      // 16

For an empty map, getMax() returns Integer.MIN_VALUE (-2147483648) and getMin() returns Integer.MAX_VALUE, with no exception. So we check getCount() > 0 before we show these numbers to a user.

Each approach behaves differently when the map is empty or holds a null value, so we pick the one whose failure case fits our data.

ApproachReturnsEmpty mapnull value
stream().max(comparingByValue())Optional entry (key and value)Optional.emptyNullPointerException
Collections.max(values())value onlyNoSuchElementExceptionNullPointerException
Collections.max(entrySet(), comparingByValue())entry (key and value)NoSuchElementExceptionNullPointerException
for loop over entrySet()entry (key and value)null variablesNullPointerException on unboxing
summaryStatistics()max, min, sum, average (no key)MIN_VALUE / MAX_VALUENullPointerException

All five approaches read every entry once, so for a HashMap each one takes time proportional to the size of the map.

4. Finding All Keys With the Max Value

When two keys share the largest value, Stream.max() and Collections.max() return only one entry. In JDK 25, both return the first matching entry in iteration order, but the Javadoc does not promise which entry we get. For a HashMap, the iteration order is not defined at all.

For example, a quiz app shows the winner of a round. When two players have the same top score, the app must show both names, not a random one of them.

So we find the max value first, and then keep every entry with that value. The following example uses a LinkedHashMap, which keeps the insertion order, so the output order is predictable.

Map<String, Integer> sales = new LinkedHashMap<>();
sales.put("apple", 8);
sales.put("banana", 3);
sales.put("cherry", 8);

Optional<Map.Entry<String, Integer>> firstTop = sales.entrySet().stream()
    .max(Map.Entry.comparingByValue());                         // Optional[apple=8] (cherry is lost)

int topSales = Collections.max(sales.values());                 // 8
List<String> topFruits = sales.entrySet().stream()
    .filter(e -> e.getValue() == topSales)
    .map(Map.Entry::getKey)
    .toList();                                                  // [apple, cherry]

The variable topSales is a primitive int, so == compares the numbers. If we store the max in an Integer variable instead, == compares two objects, and by default Java caches Integer objects only for the values from -128 to 127. For larger values, the filter misses entries, so we use equals() for Integer variables.

Map<String, Integer> visits = new LinkedHashMap<>();
visits.put("home", 1000);
visits.put("blog", 1000);

Integer topVisits = Collections.max(visits.values());
List<String> wrong = visits.entrySet().stream()
    .filter(e -> e.getValue() == topVisits)
    .map(Map.Entry::getKey)
    .toList();                                                  // [home]
List<String> right = visits.entrySet().stream()
    .filter(e -> e.getValue().equals(topVisits))
    .map(Map.Entry::getKey)
    .toList();                                                  // [home, blog]

5. Finding the Smallest and Largest Key

Sometimes we need the largest key, not the largest value, for example the latest date in a map from LocalDate to daily sales. A TreeMap keeps its keys sorted, so the smallest key is the first entry and the largest key is the last entry, and we read them without a search.

TreeMap<String, Integer> sorted = new TreeMap<>(stock);

Map.Entry<String, Integer> first = sorted.firstEntry();         // apple=5
Map.Entry<String, Integer> last = sorted.lastEntry();           // cherry=8
String maxKey = Collections.max(stock.keySet());                // "cherry" (works for any map)

For an empty TreeMap, firstEntry() and lastEntry() return null, whereas firstKey() and lastKey() throw a NoSuchElementException. A TreeMap sorts only by key, so it cannot give us the max value. To keep a map ordered by value, read sorting a Map by values.

6. Max and Min Value in a HashMap FAQs

6.1. How Do I Get the Top N Values From a HashMap?

We sort the entries by value in reverse order and keep the first N with limit(). The method reversed() needs the type arguments on comparingByValue(), because Java cannot infer them in a chain.

List<String> topTwo = stock.entrySet().stream()
    .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
    .limit(2)
    .map(Map.Entry::getKey)
    .toList();                                                  // [cherry, apple]

Sorting the whole map is fine for a few thousand entries. For a large map and a small N, a PriorityQueue of size N avoids sorting every entry.

6.2. What Happens if the Map Has null Values?

All the approaches in the table of section 3 throw a NullPointerException, because comparingByValue() and Collections.max() call compareTo() on the value. A HashMap allows null values, so we either skip them with a filter or tell the Comparator where null goes with Comparator.nullsFirst().

Map<String, Integer> withNull = new HashMap<>();
withNull.put("apple", 5);
withNull.put("banana", null);
withNull.put("cherry", 8);

Optional<Map.Entry<String, Integer>> crash = withNull.entrySet().stream()
    .max(Map.Entry.comparingByValue());                         // NullPointerException

Optional<Map.Entry<String, Integer>> skipNulls = withNull.entrySet().stream()
    .filter(e -> e.getValue() != null)
    .max(Map.Entry.comparingByValue());                         // Optional[cherry=8]
Optional<Map.Entry<String, Integer>> nullsFirst = withNull.entrySet().stream()
    .max(Map.Entry.comparingByValue(Comparator.nullsFirst(Comparator.naturalOrder())));   // Optional[cherry=8]

6.3. How Do I Find the Max When the Values Are Objects?

We compare the entries by one field of the value object. The method Comparator.comparingDouble() reads the field from each entry, so the value class does not have to implement Comparable.

record Product(String name, double price) {}

Map<String, Product> products = Map.of(
    "p1", new Product("apple", 1.5),
    "p2", new Product("cherry", 4.0),
    "p3", new Product("banana", 0.5));

Optional<Map.Entry<String, Product>> priciest = products.entrySet().stream()
    .max(Comparator.comparingDouble(e -> e.getValue().price()));   // Optional[p2=Product[name=cherry, price=4.0]]
String priciestId = priciest.map(Map.Entry::getKey).orElse("none"); // "p2"

6.4. Which Way Is the Fastest?

All of them read every entry once, so the speed is about the same for normal map sizes. The for loop finds both the max and the min in one pass, whereas two stream calls read the map twice.

7. Conclusion

To get the max or min value from a Map together with its key, we stream entrySet() and call max() or min() with Map.Entry.comparingByValue(). The result is an Optional, so an empty map is safe. For the value alone, Collections.max(map.values()) is shorter, but it throws a NoSuchElementException for an empty map.

A for loop with two if checks finds both values in one pass, and IntSummaryStatistics adds the sum and the average. When several keys share the top value, we find the max first and filter for it, and we compare Integer objects with equals().

8. References

Happy Learning !!

Source Code on Github

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.