<code>ConcurrentModificationException</code> in Java

ConcurrentModificationException is thrown when a collection's structure changes while it's being iterated. Surprisingly, it's not about threads β€” it's thrown even from single-threaded code that modifies a collection inside its own for loop.

The classic trigger

List<String> xs = new ArrayList<>(List.of("a", "b", "c"));
for (String s : xs) {
    if (s.equals("b")) xs.remove(s);       // ❌ CME
}

The enhanced for uses an Iterator behind the scenes. Iterators track a modification count and throw CME when they detect a change that didn't go through the iterator.

The three safe patterns

// 1. Iterator.remove()
var it = xs.iterator();
while (it.hasNext()) {
    if (it.next().equals("b")) it.remove();
}

// 2. removeIf (Java 8+) β€” cleanest
xs.removeIf(s -> s.equals("b"));

// 3. Collect what to remove, then removeAll
var toRemove = xs.stream().filter(s -> s.equals("b")).toList();
xs.removeAll(toRemove);

Adding during iteration

You can't add either. Build a separate list and combine:

var toAdd = new ArrayList<String>();
for (String s : xs) {
    if (s.length() == 1) toAdd.add(s + "!");
}
xs.addAll(toAdd);

Multi-threaded reads + writes

For true concurrent access across threads, use a concurrent collection β€” CopyOnWriteArrayList (read-heavy), ConcurrentHashMap (general-purpose map). They don't throw CME but have their own semantics (e.g. the iterator is a snapshot).

Maps β€” use entrySet iterator

Map<String,Integer> votes = ...;
for (var it = votes.entrySet().iterator(); it.hasNext(); ) {
    var e = it.next();
    if (e.getValue() == 0) it.remove();
}

Common mistakes

  • Assuming CME is a threading issue β€” it mostly isn't. Single-threaded add/remove during iteration triggers it.
  • Using a for (int i ...) loop to "avoid" CME β€” you'll skip elements when remove shifts indexes.
  • Synchronising a non-concurrent collection for writes but reading without the lock β€” race conditions and possibly CME.

Related

Pillar: Java exceptions. See also Iterator and removeIf.