Back to Blog
Java

Java ConcurrentModificationException: Causes and Fixes

java concurrent modification exception: Why Java throws ConcurrentModificationException during iteration, how the fail-fast iterator detects changes, and the reliable...

ConcurrentModificationExceptionJava CollectionsIteratorfail-fastArrayListremoveIf
Illustration of a Java iterator detecting a list modification and throwing ConcurrentModificationException

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

A ConcurrentModificationException appears when code modifies a collection while an iterator is actively traversing it. The most common place developers hit it is a for-each loop over an ArrayList or HashMap where the loop body removes or adds an element.

List<String> items = new ArrayList<>(List.of("a", "b", "c")); for (String item : items) { if (item.equals("b")) { items.remove(item); } }

Running this throws java.util.ConcurrentModificationException at the next call to next() inside the loop. The exception name is misleading: no second thread is involved. The modification happens in the same thread, between iterator calls.

What Triggers a ConcurrentModificationException

The exception is triggered by any structural modification to the collection during iteration. Adding, removing, or clearing elements all qualify. Changing the value of an existing element does not, because the iterator still sees a consistent structure. This distinction matters when debugging: a loop that only calls set() on a List will not throw, while a loop that calls add() or remove() will.

A Minimal Reproduction

The smallest reliable reproduction is removing an element from a list while iterating it with an enhanced for loop. The enhanced for loop is syntactic sugar for an Iterator, so the failure is identical whether you write for (String item : items) or use Iterator directly.

List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5)); for (Integer number : numbers) { if (number % 2 == 0) { numbers.remove(number); } }

The exception is thrown on the iteration after the removal, not at the moment of the removal itself. That timing confuses debugging because the stack trace points at the loop header rather than the remove call.

Why the Iterator Detects the Modification

ArrayList, HashMap, and most other collection classes in java.util maintain an internal integer field called modCount. Every structural modification increments it. When you create an iterator, it records the current modCount. On each call to next(), the iterator compares the recorded value with the collection's current value. If they differ, it throws ConcurrentModificationException.

This design is called fail-fast. The iterator fails quickly instead of continuing with an inconsistent view of the collection, which could produce silent data corruption or infinite loops. The behavior is not guaranteed by the specification in every scenario, but the standard implementations in the JDK follow it.

The same mechanism applies to HashMap and HashSet. Iterating a HashMap while inserting a new key that triggers a resize will throw the same exception.

Removing Items While Iterating

The correct way to remove the current element during iteration is through the iterator itself.

Iterator<String> iterator = items.iterator(); while (iterator.hasNext()) { String item = iterator.next(); if (item.equals("b")) { iterator.remove(); } }

Iterator.remove() is the only removal operation that keeps the iterator's expected modCount in sync, because the iterator performs the removal through the collection and updates its own recorded value. Calling list.remove() inside the loop bypasses that synchronization and triggers the exception.

For the common case of removing elements that match a predicate, Collection.removeIf is cleaner and avoids the iterator entirely.

items.removeIf(item -> item.equals("b"));

removeIf iterates internally and removes matching elements in a single pass, so it does not throw a ConcurrentModificationException.

Collecting and Removing After the Loop

When the removal condition depends on information gathered across the whole iteration, collect the elements to remove first, then remove them after the loop finishes.

List<String> toRemove = new ArrayList<>(); for (String item : items) { if (item.startsWith("temp")) { toRemove.add(item); } } items.removeAll(toRemove);

This approach is useful when the decision to remove an element depends on elements seen later in the iteration. It also works when you need to remove from multiple collections at once. The tradeoff is the extra memory for the toRemove list, which matters only for very large collections.

Multi-Threaded Modification

The exception also appears when one thread iterates a collection while another thread modifies it. Synchronizing the collection with Collections.synchronizedList does not prevent this, because synchronization protects individual method calls but not the entire iteration. The iterator holds a snapshot of modCount from when it was created, and the other thread's modification changes the collection's count.

List<String> shared = Collections.synchronizedList(new ArrayList<>()); // Thread A synchronized (shared) { for (String item : shared) { // processing } } // Thread B synchronized (shared) { shared.add("new"); }

If both threads hold the same lock for the whole iteration, the exception is avoided. But holding a lock during iteration blocks other threads for the duration of the loop, which can become a bottleneck.

For concurrent access without blocking, CopyOnWriteArrayList and ConcurrentHashMap are the standard alternatives. Their iterators operate on a snapshot or are weakly consistent, so they do not throw ConcurrentModificationException when the underlying collection changes.

Runtime Cost and Choosing an Alternative

The choice of fix affects runtime behavior. Iterator.remove() on an ArrayList shifts all subsequent elements left on every removal, so removing many elements one by one is O(n) per removal. removeIf performs a single pass and compacts the array once, which is more efficient when many elements match.

CopyOnWriteArrayList makes iteration cheap and safe, but every write copies the entire backing array. It is appropriate when reads dominate and writes are rare, not for frequently mutated collections.

ConcurrentHashMap trades some iteration consistency for scalability. Its iterators reflect the state of the map at some point during traversal and are not guaranteed to reflect later changes, but they never throw a ConcurrentModificationException. Use it when multiple threads read and write the same map concurrently.

When the Exception Does Not Appear

Some collections intentionally avoid fail-fast behavior. CopyOnWriteArrayList and CopyOnWriteArraySet create a snapshot of the array when the iterator is created, so modifications after that point are invisible to the iteration. ConcurrentHashMap uses weakly consistent iterators that may or may not see concurrent updates.

This means the same code pattern that throws on an ArrayList runs without error on a CopyOnWriteArrayList. The absence of the exception is not a sign that the code is correct — it means the iteration may be working on stale data. When the modification must be visible to the iteration, the fail-fast behavior of ArrayList is actually the safer outcome because it surfaces the bug immediately.

java concurrent modification exception: Practical Usage and | RYUSLOG DEV