Introduction
ConcurrentModificationException is thrown when a collection is structurally modified while being iterated, except through the iterator's own remove() method. The for-each loop uses an iterator internally, so calling list.remove() inside a for-each loop modifies the collection's modCount without updating the iterator's expected count, triggering the exception.
Symptoms
java.util.ConcurrentModificationExceptionatArrayList$Itr.next()- Exception occurs during iteration, not at the actual remove call
- Works when removing a single element but fails with multiple
- Non-deterministic behavior depending on which elements are removed
```java // WRONG - throws ConcurrentModificationException List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Charlie")); for (String name : names) { if (name.startsWith("B")) { names.remove(name); // CME on next iteration! } }
// Exception: // java.util.ConcurrentModificationException // at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013) // at java.util.ArrayList$Itr.next(ArrayList.java:967) ```
Common Causes
- Removing elements from a list during for-each iteration
- Modifying a collection in a stream's forEach lambda
- Multiple threads accessing the same ArrayList without synchronization
- Adding elements during iteration (also triggers CME)
- Nested loops modifying the same collection
Step-by-Step Fix
- 1.Use Iterator.remove():
- 2.```java
- 3.// CORRECT - use iterator's own remove method
- 4.List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Charlie"));
- 5.Iterator<String> it = names.iterator();
- 6.while (it.hasNext()) {
- 7.String name = it.next();
- 8.if (name.startsWith("B")) {
- 9.it.remove(); // Safe - iterator knows about the removal
- 10.}
- 11.}
- 12.// Result: [Alice, Charlie]
- 13.
` - 14.Use removeIf (Java 8+):
- 15.```java
- 16.// CORRECT - cleanest approach
- 17.List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Charlie"));
- 18.names.removeIf(name -> name.startsWith("B"));
- 19.// Result: [Alice, Charlie]
- 20.
` - 21.Collect to a new list:
- 22.```java
- 23.// CORRECT - create a new filtered list
- 24.List<String> names = List.of("Alice", "Bob", "Charlie");
- 25.List<String> filtered = names.stream()
- 26..filter(name -> !name.startsWith("B"))
- 27..collect(Collectors.toList());
// Or with traditional loop List<String> toRemove = new ArrayList<>(); for (String name : names) { if (name.startsWith("B")) { toRemove.add(name); // Collect first, remove later } } names.removeAll(toRemove); // Safe - not iterating ```
- 1.Use CopyOnWriteArrayList for concurrent modification:
- 2.```java
- 3.// For scenarios where modification during iteration is expected
- 4.List<String> names = new CopyOnWriteArrayList<>(List.of("Alice", "Bob", "Charlie"));
// Safe to modify during iteration (iterator uses snapshot) for (String name : names) { if (name.startsWith("B")) { names.remove(name); // No CME - iterator uses copy } }
// WARNING: CopyOnWriteArrayList copies the entire array on each write // Only use for read-heavy, write-rarely scenarios ```
- 1.Thread-safe iteration with synchronization:
- 2.```java
- 3.List<String> names = Collections.synchronizedList(new ArrayList<>());
// Must synchronize on the list during iteration synchronized (names) { Iterator<String> it = names.iterator(); while (it.hasNext()) { String name = it.next(); if (shouldRemove(name)) { it.remove(); } } } ```
Prevention
- Prefer
removeIf()for conditional removal - it is both safe and readable - Never call
list.remove()inside a for-each loop - Use
CopyOnWriteArrayListonly for read-heavy, write-rarely patterns - Use
ConcurrentHashMapfor concurrent map access instead ofCollections.synchronizedMap - Add unit tests that exercise removal during iteration
- Use
@GuardedByannotations to document thread safety requirements - In Spring, use
@Transactionalproperly to avoid concurrent collection modification in service layers