To find the max value in a Java Map together with its key, or the min value, we stream the entrySet() and call max(Map.Entry.comparingByValue()) or min(Map.Entry.comparingByValue()), which return an Optional with the whole entry. When only the number matters, Collections.max(map.values()) and Collections.min(map.values()) return the value alone.
We need the max or min value of a map whenever it holds counts, scores or prices, for example to show the best-selling product from a map of sales per product, or the cheapest shop from a map of prices per shop.
The following example finds the largest and the smallest stock level in a small fruit map, 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
The stream version returns an Optional, so an empty map gives Optional.empty, whereas Collections.min() throws a NoSuchElementException for an empty map. We go through streams, Collections and a single loop that finds both values in one pass, and finish with the cases that break the short version, such as ties, null values and value objects.
1. Using Java Streams
A Map is not a collection of values, so it has no max() method of its own, and we stream one of its views instead. The entrySet() view gives us Map.Entry objects, each holding a key and its value, and Stream.max() and Stream.min() pick the largest or smallest element with a Comparator.

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.
A reporting page in a shop shows the product with the most sales this month. The sales are stored in a Map from product name to quantity, and the page needs the product name, not only the number, so it works with the entry.
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]
String maxKey = maxEntry.map(Map.Entry::getKey).orElse("none"); // "cherry"
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 without boxing.
Map<String, Integer> stock = Map.of("apple", 5, "banana", 3, "cherry", 8);
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.
Map<String, Integer> empty = new HashMap<>();
String noKey = empty.entrySet().stream().max(Map.Entry.comparingByValue()).map(Map.Entry::getKey).orElse("none"); // "none"
Map.Entry<String, Integer> crash = empty.entrySet().stream().max(Map.Entry.comparingByValue()).get(); // NoSuchElementException
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. A Map.Entry is not Comparable, so for entries we pass Map.Entry.comparingByValue().
Map<String, Integer> stock = Map.of("apple", 5, "banana", 3, "cherry", 8);
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 cannot return an empty result. For an empty map, both methods 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. 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, because the first entry is both the max and the min at the start.
Map<String, Integer> stock = Map.of("apple", 5, "banana", 3, "cherry", 8);
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;
}
}
String largest = maxEntry.toString(); // "cherry=8"
String smallest = minEntry.toString(); // "banana=3"
Both variables stay null for an empty map, so real code checks them 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 when the values arrive in ascending order, such as 3, 5 and 8, the min stays null.
3.1. Max, Min, Sum and Average With IntSummaryStatistics
When the values are numbers and a report needs several results at once, IntSummaryStatistics collects the max, the min, the count, the sum and the average in one pass. The class gives only the values, not the keys.
Map<String, Integer> stock = Map.of("apple", 5, "banana", 3, "cherry", 8);
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 and getMin() returns Integer.MAX_VALUE, with no exception. So we check getCount() > 0 before we show these numbers to a user.
3.2. Comparing the Max and Min Approaches
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. All of them read every entry once, so for a map with n entries each one takes O(n) time, and none is faster than the others in a way that matters.
| Approach | Returns | Empty map | null value |
|---|---|---|---|
| stream().max(comparingByValue()) | Optional entry (key and value) | Optional.empty | NullPointerException |
| Collections.max(values()) | Value only | NoSuchElementException | NullPointerException |
| Collections.max(entrySet(), comparingByValue()) | Entry (key and value) | NoSuchElementException | NullPointerException |
| for loop over entrySet() | Max and min entries in one pass | null variables | NullPointerException on unboxing |
| summaryStatistics() | Max, min, sum, average (no key) | MIN_VALUE and MAX_VALUE | NullPointerException |
4. Finding All Keys With the Max Value
When two keys share the largest value, Stream.max() and Collections.max() return only one entry. On JDK 25, both keep the first matching entry in iteration order, but the Javadoc does not promise which one we get, and a HashMap has no defined iteration order at all.
A quiz app shows the winner of each round. When two players share the top score, the app must show both names, not one of them picked by the hash order. So we find the max value first and keep every entry with that value. The example uses a LinkedHashMap, which keeps insertion order, so the output order is predictable.
Map<String, Integer> scores = new LinkedHashMap<>();
scores.put("anna", 8);
scores.put("ben", 3);
scores.put("carl", 8);
Optional<Map.Entry<String, Integer>> firstTop = scores.entrySet().stream().max(Map.Entry.comparingByValue()); // Optional[anna=8]
int topScore = Collections.max(scores.values()); // 8
List<String> winners = scores.entrySet().stream().filter(e -> e.getValue() == topScore).map(Map.Entry::getKey).toList(); // [anna, carl]
The variable topScore is a primitive int, so == compares numbers. If we keep the max in an Integer variable instead, == compares two objects, and Java caches Integer objects only for values from -128 to 127 by default. For larger values the filter misses entries, so Integer variables need equals().
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 reading them needs no search. Since Java 21, a LinkedHashMap has the same two methods through the SequencedMap interface, but there they return the first and last entry in insertion order.
Map<String, Integer> stock = Map.of("apple", 5, "banana", 3, "cherry", 8);
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"
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 Value in a Java Map FAQs
Readers who have the basic max and min calls working ask these questions next.
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.
Map<String, Integer> stock = Map.of("apple", 5, "banana", 3, "cherry", 8);
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.2 throw a NullPointerException, because comparingByValue() and Collections.max() call compareTo() on each 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. Can I Get the Max and Min in One Stream?
Yes. Collectors.teeing(), added in Java 12, runs two collectors over the same stream and combines their results, so the map is read once.
Map<String, Integer> stock = Map.of("apple", 5, "banana", 3, "cherry", 8);
Comparator<Map.Entry<String, Integer>> byValue = Map.Entry.comparingByValue();
String range = stock.entrySet().stream().collect(Collectors.teeing(Collectors.minBy(byValue), Collectors.maxBy(byValue), (lo, hi) -> lo.map(Map.Entry::getKey).orElse("-") + " to " + hi.map(Map.Entry::getKey).orElse("-"))); // "banana to cherry"
The teeing() collector is shorter to read in a stream pipeline, whereas the loop in section 3 is easier to debug.
7. Conclusion
To get the max or min value of 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, comparing Integer objects with equals().
8. References
- Map.Entry Javadoc (Java 25)
- Collections Javadoc (Java 25)
- Stream Javadoc (Java 25)
- IntSummaryStatistics Javadoc (Java 25)
Happy Learning !!