Back to Blog
Java

Java Map Entry: Working with Entry Objects in Maps

java map entry: Learn how to use Java Map.Entry to iterate over map entries, update values, and avoid common pitfalls in your Java applications.

javamapentrysetiterationhashmapcollection
Diagram showing a Java Map with key-value pairs and an entry object highlighted for iteration.

java map entry requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

When you work with a Map in Java, each stored association is a key-value pair. The Map.Entry interface models that pair. It defines three methods: getKey(), getValue(), and setValue(V value). The entrySet() method on any Map implementation returns a Set<Map.Entry<K, V>>, which is a view of all pairs in the map. Iterating over this set is the standard way to access both keys and values together.

The entry objects returned by entrySet() are not detached copies. They are backed by the map itself. This means setValue() can change the value stored in the map, but it cannot change the key. The key is immutable in the context of the entry.

What a Map.Entry Actually Represents

The Map.Entry interface is a nested type inside java.util.Map. It exists to give you a uniform way to handle a single key-value pair without needing to know the underlying map implementation. Every map implementation, from HashMap to TreeMap to ConcurrentHashMap, provides its own internal entry objects that implement this interface.

The three methods are straightforward:

  • K getKey() returns the key.
  • V getValue() returns the current value.
  • V setValue(V newValue) replaces the value and returns the old value.

Because the entry is a view, calling setValue() modifies the map immediately. This is the primary reason to use Map.Entry during iteration rather than calling put() separately.

Iterating Over Entries with a For-Each Loop

The most common usage is a simple for-each loop:

Map<String, Integer> scores = new HashMap<>(); scores.put("Alice", 90); scores.put("Bob", 85); for (Map.Entry<String, Integer> entry : scores.entrySet()) { System.out.println(entry.getKey() + " -> " + entry.getValue()); }

This loop works because entrySet() returns a Set that supports iteration. Each entry is a live view of one key-value pair. The loop does not need to call map.get(key) again, which avoids an extra lookup and makes the code clearer.

The order of iteration depends on the Map implementation. HashMap does not guarantee order, while LinkedHashMap preserves insertion order, and TreeMap sorts by key. If order matters, choose the implementation accordingly.

Updating Values Through an Entry

Map.Entry provides setValue(), which replaces the value for the current key. This is useful when you need to transform values while iterating without causing a ConcurrentModificationException.

Map<String, Integer> prices = new HashMap<>(); prices.put("apple", 2); prices.put("banana", 3); for (Map.Entry<String, Integer> entry : prices.entrySet()) { entry.setValue(entry.getValue() * 2); }

After this loop, both prices are doubled. The setValue() method modifies the underlying map directly. This is safer than calling map.put(key, newValue) inside the loop, because put during iteration may throw ConcurrentModificationException depending on the map implementation. setValue() is designed to be safe during iteration.

Note that setValue() is optional for some implementations. For example, unmodifiable entries from Collections.unmodifiableMap() throw UnsupportedOperationException. Always check the map's mutability before relying on this method.

Comparing entrySet(), keySet(), and values()

Many developers wonder when to use entrySet() instead of keySet() or values(). The choice depends on what you need.

MethodReturnsGives access toTypical use case
entrySet()Set<Map.Entry<K, V>>Both key and valueWhen you need both in each iteration
keySet()Set<K>Only keysWhen you only need keys
values()Collection<V>Only valuesWhen you only need values

Using keySet() and then calling map.get(key) inside the loop works, but it adds a second lookup per iteration. For large maps, this doubles the hash computation cost. entrySet() avoids that by giving you the value directly. If you need both key and value, prefer entrySet().

There is also a subtle difference about the returned collection's type. keySet() and values() are not Set types; values() is a Collection. This matters if you rely on set semantics like uniqueness or ordering guarantees.

Using Entry in Streams and Lambdas

Java 8 streams work well with entrySet(). You can filter, map, and collect entries without writing explicit loops.

Map<String, Integer> ages = new HashMap<>(); ages.put("Zoe", 30); ages.put("Max", 17); List<String> adults = ages.entrySet() .stream() .filter(entry -> entry.getValue() >= 18) .map(Map.Entry::getKey) .toList();

The entrySet() stream gives you a Stream<Map.Entry<K, V>>. This is convenient when you need to transform the map into another structure or extract a subset of keys based on values. The Map.Entry methods work as method references, making the pipeline readable.

For simple iteration that only performs a side effect, Map.forEach() is more concise:

ages.forEach((key, value) -> System.out.println(key + " is " + value + " years old"));

forEach accepts a BiConsumer and is implemented directly by the map. It does not create an intermediate Set view, which can be slightly more efficient in some cases. However, forEach does not give you access to the Entry object itself, so you cannot call setValue() inside it. If you need to modify values, stick with entrySet().

Performance and Memory Considerations

The performance of iterating over entrySet() depends on the map implementation. For HashMap, the entries are stored in an array of Node objects. Iterating over entrySet() visits each node directly. This is O(n) and does not require any additional hash lookups.

In contrast, if you iterate over keySet() and call get() for each key, you perform an additional hash lookup per key. This is also O(n) but with a higher constant factor. For maps with a large number of entries, the extra lookups add measurable overhead.

Memory usage is another factor. entrySet() returns a view backed by the map, so it does not allocate a new collection. The Entry objects you receive are the same objects stored in the map (for HashMap, they are Node instances). This means there is no extra memory allocation for the iteration itself. However, if you collect the entries into a new list, you will create new references, but the underlying data is shared.

Concurrency is a concern when you modify the map while iterating. The fail-fast behavior of most Map implementations throws ConcurrentModificationException if the map is structurally modified (adding or removing entries) during iteration. Changing a value with setValue() is not a structural modification, so it is allowed. Removing an entry via Iterator.remove() is also allowed because it uses the iterator's own removal mechanism.

If you need to remove entries while iterating, use the iterator explicitly:

Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, Integer> entry = iterator.next(); if (entry.getValue() < 0) { iterator.remove(); } }

This avoids ConcurrentModificationException because the iterator is aware of the removal.

Common Pitfalls with Null Keys and Values

HashMap allows one null key and multiple null values. When you iterate over entries, null values are possible. Code that assumes getValue() is never null can break.

Map<String, String> config = new HashMap<>(); config.put("timeout", null); for (Map.Entry<String, String> entry : config.entrySet()) { if (entry.getValue().equals("5")) { // NullPointerException // ... } }

Check for null explicitly if your map may contain null values. Also, some map implementations like TreeMap do not allow null keys, but they do allow null values. The behavior depends on the implementation. Always document or validate the expected nullability of your map contents.

When to Use Map.Entry Directly vs. a Custom Class

Sometimes developers consider creating a custom class to hold key-value pairs instead of using Map.Entry. This is usually unnecessary. Map.Entry is a standard interface that works with all map implementations. It provides the exact functionality you need for iteration and value updates.

However, there are cases where you might want a separate record class, for example, when you need to pass pairs around outside the context of a map, or when you need additional behavior. In those cases, you can create a simple record:

record Pair<K, V>(K key, V value) {}

But for map operations, using Map.Entry keeps the code idiomatic and avoids unnecessary conversions. The entry objects are already integrated with the map's internal structure, so using them is more efficient than copying data into a new object.