Fundamentals 12 min read

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.

Programmer XiaoFu
Programmer XiaoFu
Programmer XiaoFu
Why Deleting or Adding Entries During a HashMap foreach Loop Triggers ConcurrentModificationException

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;
}
modCount

increments 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaHashMapCollectionsIteratorforeachConcurrentModificationExceptionIterator.removeJDK5
Programmer XiaoFu
Written by

Programmer XiaoFu

xiaofucode.com – a programmer learning guide driven by the pursuit of profit

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.