Why Deleting or Adding Entries During a HashMap foreach Loop Triggers ConcurrentModificationException
The article explains why modifying a HashMap—by removing, adding, or putting entries—while iterating with a Java foreach loop causes a ConcurrentModificationException, demonstrates the underlying bytecode transformation, and shows how to safely perform such operations using an Iterator.
A colleague encountered a ConcurrentModificationException when scanning a HashMap with a foreach loop, prompting an investigation into the root cause.
What the foreach syntax does
Introduced in Java 5, the foreach statement is compiled to an Iterator call for collections and to an index loop for arrays. The compiler hides this transformation.
Bytecode comparison
Two simple examples illustrate the compilation result.
public class HashMapIteratorDemo {
String[] arr = {"aa", "bb", "cc"};
public void test1() {
for (String str : arr) {
}
}
}The above is compiled to bytecode that iterates over the array.
public class HashMapIteratorDemo2 {
String[] arr = {"aa", "bb", "cc"};
public void test1() {
for (int i = 0; i < arr.length; i++) {
String str = arr[i];
}
}
}Both bytecode listings are almost identical, confirming that foreach on collections is translated to iterator calls.
Foreach on a collection vs explicit iterator
public class HashMapIteratorDemo3 {
List<Integer> list = new ArrayList<>();
public void test1() {
list.add(1);
list.add(2);
list.add(3);
for (Integer var : list) {
}
}
} public class HashMapIteratorDemo4 {
List<Integer> list = new ArrayList<>();
public void test1() {
list.add(1);
list.add(2);
list.add(3);
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
Integer var = it.next();
}
}
}HashMap traversal and element remove/put/add
1. Phenomenon
Iterating and printing entries works fine:
public class HashMapIteratorDemo5 {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
map.put(1, "aa");
map.put(2, "bb");
map.put(3, "cc");
for (Map.Entry<Integer, String> entry : map.entrySet()) {
int k = entry.getKey();
String v = entry.getValue();
System.out.println(k + " = " + v);
}
}
}Modifying an existing entry (changing the value of key 1) succeeds, but adding a new entry inside the foreach loop throws an exception:
public class HashMapIteratorDemo5 {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
map.put(1, "aa");
map.put(2, "bb");
map.put(3, "cc");
for (Map.Entry<Integer, String> entry : map.entrySet()) {
int k = entry.getKey();
if (k == 1) {
map.put(4, "AA"); // add new entry
}
String v = entry.getValue();
System.out.println(k + " = " + v);
}
}
}The runtime throws ConcurrentModificationException, confirming that a put that adds a new mapping is unsafe during foreach iteration.
2. Underlying mechanism
The Javadoc for Map.entrySet() states that modifying the map while iterating (except via the iterator’s own remove) yields undefined results. The iterator implementation checks a modification count:
final Node<K,V> nextNode() {
Node<K,V>[] t;
Node<K,V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
if ((next = (current = e).next) == null && (t = table) != null) {
do {} while (index < t.length && (next = t[index++]) == null);
}
return e;
} modCountincrements on any structural change; expectedModCount is captured when the iterator is created. A mismatch triggers the exception.
Removal implementations all delegate to removeNode, which increments modCount. Only the iterator’s own remove synchronises expectedModCount after the call:
public final void remove() {
Node<K,V> p = current;
if (p == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
current = null;
K key = p.key;
removeNode(hash(key), key, null, false, false);
expectedModCount = modCount; // sync counts
}Consequently, using the iterator’s remove method avoids the exception, while direct map.put(...) or map.remove(...) does not.
Correct usage
Iterate with an explicit iterator and invoke it.remove() when needed:
public class HashMapIteratorDemo5 {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
map.put(1, "aa");
map.put(2, "bb");
map.put(3, "cc");
Iterator<Map.Entry<Integer, String>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Integer, String> entry = it.next();
int key = entry.getKey();
if (key == 1) {
it.remove(); // safe removal
}
}
}
}Using the iterator’s removal method keeps expectedModCount in sync with modCount, preventing ConcurrentModificationException during traversal.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer XiaoFu
xiaofucode.com – a programmer learning guide driven by the pursuit of profit
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
