Back to Blog
Java

Java Map entrySet() for Iteration and Modification

Learn how to use Java's Map.entrySet() method to iterate over key-value pairs, modify entries, and understand performance and concurrency trade-offs.

JavaMapentrySetiterationperformanceconcurrency
Diagram of a Java Map with key-value pairs being iterated through entrySet, showing direct access to each entry.

Java's Map.entrySet() method returns a Set view of the key-value pairs in a map. It is the standard way to iterate over both keys and values, and it also lets you update values or remove entries while iterating. This article explains the method's behavior, its performance characteristics, and the situations where another view might be a better fit.

The entrySet() method on java.util.Map returns a Set of Map.Entry objects, each representing a key-value pair. This is the most direct way to access both keys and values during iteration. Here's a minimal example:

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()); }

The entrySet() view is backed by the map, so changes you make through the view are reflected in the original map. This is particularly useful when you need to update values while iterating.

What entrySet() Returns and Why It Matters

entrySet() returns a Set<Map.Entry<K, V>>. The Map.Entry interface provides getKey(), getValue(), and setValue(). The returned set is a view, not a copy. Modifying the set (for example, removing an entry via Iterator.remove()) modifies the underlying map. This behavior is consistent across the common Map implementations, including HashMap, TreeMap, and LinkedHashMap.

Understanding that entrySet() is a view is critical: it is not a snapshot. The returned set reflects the current state of the map, so you can iterate and modify entries without keeping a separate copy.

Iterating Over a Map with entrySet()

The most common pattern is the enhanced for loop shown above. You can also use an explicit iterator:

Iterator<Map.Entry<String, Integer>> iterator = scores.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, Integer> entry = iterator.next(); // process entry }

The explicit iterator is useful when you need to remove entries during iteration. The Iterator.remove() method is the supported way to remove the current entry without triggering a ConcurrentModificationException on the next iterator call.

Iterator<Map.Entry<String, Integer>> iterator = scores.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, Integer> entry = iterator.next(); if (entry.getValue() < 60) { iterator.remove(); // safe removal } }

This is the recommended way to remove entries while iterating, as opposed to calling map.remove() inside the loop, which would cause a ConcurrentModificationException in most non-concurrent implementations.

Modifying Values During Iteration

The setValue() method on Map.Entry allows you to update the value associated with the current key. This is more efficient than calling map.put(key, newValue) because it avoids a second lookup for the key.

for (Map.Entry<String, Integer> entry : scores.entrySet()) { if (entry.getValue() < 70) { entry.setValue(entry.getValue() + 10); // boost low scores } }

The common Map implementations—HashMap, TreeMap, and LinkedHashMap—support setValue() on entries returned by entrySet(). A custom map can return entries that do not support this operation, in which case setValue() throws UnsupportedOperationException. If you rely on this behavior, check the documentation for your map implementation.

entrySet() vs keySet() vs values()

Choosing the right view depends on what you need to access. Here's a quick comparison:

ViewReturnsUse case
entrySet()Set<Map.Entry<K,V>>Need both keys and values, or need to modify values
keySet()Set<K>Only keys are needed, or you need to remove entries by key
values()Collection<V>Only values are needed, no key access

Using keySet() and then calling map.get(key) for each key is common but less efficient. It performs a separate hash lookup for every key, which adds overhead. entrySet() avoids that by providing direct access to the value. If you only need keys, keySet() is more concise and avoids dealing with Map.Entry objects.

Performance and Memory Considerations

The primary performance advantage of entrySet() is avoiding the extra get() call. In a loop over a large map, that can be significant. With HashMap, get() has O(1) average complexity, but each lookup still requires hashing and bucket traversal. Iterating with entrySet() gives you the value directly from the current entry, avoiding that work.

Memory-wise, entrySet() does not create a new collection. It returns a view backed by the map. For HashMap, TreeMap, and LinkedHashMap, the Map.Entry objects are typically the same nodes stored in the map, so iterating with entrySet() does not allocate new entry objects. The allocation behavior is implementation-specific, however; a map that stores keys and values separately may create a new Map.Entry on each iteration. If allocation matters, verify the behavior of the map implementation you are using.

Concurrency and Thread-Safety

entrySet() itself is not thread-safe. If the map is modified while another thread is iterating over entrySet(), the iterator will throw ConcurrentModificationException (fail-fast behavior). This is true for most non-concurrent map implementations.

For concurrent access, use ConcurrentHashMap. Its entrySet() iterator is weakly consistent: it reflects the state of the map at some point during iteration, but does not throw ConcurrentModificationException if the map is modified concurrently. This allows safe iteration without external synchronization, though you may see stale or partial updates.

If you need to modify the map while iterating in a multi-threaded context, consider using ConcurrentHashMap and its compute or merge methods, which provide atomic updates. Alternatively, synchronize on the map externally, but that can hurt scalability.

When to Avoid entrySet() (or Use It Carefully)

There are a few situations where entrySet() might not be the best choice:

  • You only need keys: Use keySet() to avoid the overhead of accessing Map.Entry objects, even if the overhead is small.
  • You only need values: Use values() for clarity and simplicity.
  • You need to add entries during iteration: Adding new entries (as opposed to updating values for existing keys) will cause a ConcurrentModificationException in most non-concurrent maps. Collect the new entries and add them after the loop, or use ConcurrentHashMap with a weakly consistent iterator.
  • You need to remove entries: Iterator.remove() on an entrySet() iterator is the standard approach, but if you only need to remove by key, keySet() may be more direct.
  • You are working with a custom map that returns immutable entries: If setValue() is not supported, you'll need to use map.put() instead, which may be less efficient.

entrySet() does not create a list, so the iteration order is whatever the map implementation provides. HashMap does not guarantee an order; TreeMap and LinkedHashMap do. If you need to process entries in a specific order, sort a list created from the entry set or use the Stream API:

Map<String, Integer> topScores = scores.entrySet().stream() .filter(e -> e.getValue() >= 90) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

This pattern is concise, but note that Collectors.toMap() throws IllegalStateException on duplicate keys. In this example the keys come from a Map, so duplicates are not a practical issue, but the merge-function variant is useful if you reuse the pattern on a stream that can contain duplicates.

In production, the choice between entrySet() and other views often comes down to readability and the specific operation you need. For most iteration tasks that require both key and value, entrySet() is the idiomatic and efficient choice.