Java ConcurrentModificationException
Java ConcurrentModificationException
ConcurrentModificationException is one of the most frequently encountered runtime exceptions in Java, and also one of the most misunderstood. Despite having "concurrent" in its name, it fires just as often in completely single-threaded code. The exception exists for one reason: to tell you immediately that a collection was structurally modified while an iterator was actively traversing it — something that produces undefined, unpredictable results if allowed to continue silently.
Understanding exactly what triggers it, what does not, and how to fix each case is non-negotiable for any Java developer working with collections.
What Is ConcurrentModificationException?
java.util.ConcurrentModificationException is an unchecked runtime exception — it extends RuntimeException and the compiler never requires you to declare or catch it. It is thrown by the iterator of a collection when that collection is structurally modified outside the iterator while the iterator is active.
EXCEPTION HIERARCHY:
java.lang.Throwable
└── java.lang.Exception
└── java.lang.RuntimeException
└── java.util.ConcurrentModificationException ← unchecked
PACKAGE: java.util
SINCE: Java 1.2
KEY FACTS:
- Unchecked — no try-catch required, no throws declaration needed
- Signals a programming error, not a recoverable condition
- Thrown by fail-fast iterators in java.util package collections
- NOT thrown by fail-safe iterators in java.util.concurrent package
- Can fire in single-threaded code — no multiple threads required
- "Best-effort" detection — JVM spec does not guarantee it fires every time
What "Structural Modification" Means
A structural modification is any operation that changes the number of elements in the collection — any add(), remove(), clear(), or addAll(). It does NOT include operations that only replace an element's value, like List.set(index, newValue) — those are not structural because the collection's size does not change.
STRUCTURAL MODIFICATIONS (increment modCount → may trigger CME): list.add(element) list.add(index, element) list.remove(index) list.remove(object) list.addAll(collection) list.removeAll(collection) list.retainAll(collection) list.clear() list.sort() ← uses modCount to track reordering NOT STRUCTURAL (do NOT increment modCount → safe during iteration): list.get(index) list.set(index, newValue) ← replaces, does not change size list.contains(object) list.size() list.isEmpty() iterator.remove() ← safe because iterator syncs modCount itself list.removeIf(predicate) ← safe because handles modCount internally
The modCount Mechanism — How CME Is Detected
Every java.util collection that supports fail-fast iteration maintains an int field called modCount. This counter starts at 0 and increments on every structural modification.
When an iterator is created via collection.iterator(), it snapshots the current modCount as its own expectedModCount. Every next() call begins with one check:
MODCOUNT CHECK ON EVERY next() CALL:
if (modCount != expectedModCount)
throw new ConcurrentModificationException()
Example — ArrayList with 3 elements (modCount = 3):
Iterator created:
cursor = 0
expectedModCount = 3 ← snapshot taken here
Iteration step 1:
modCount (3) == expectedModCount (3) ← OK
returns element at index 0
cursor = 1
External add() called:
modCount becomes 4 ← snapshot is now stale
Iteration step 2:
modCount (4) != expectedModCount (3) ← MISMATCH
throw ConcurrentModificationException ← fires immediately
IMPORTANT: The exception fires on the NEXT next() call after the
modification — NOT at the moment of modification.
The modCount check is described in the Java specification as "best effort" — the JVM does not guarantee CME fires on every structural modification. In practice it fires reliably for standard collection implementations, but you should never write code that relies on CME firing to detect correctness problems.
When ConcurrentModificationException Fires
Scenario 1 — Direct Modification Inside for-each Loop
The most common CME pattern in production code. The for-each loop compiles to an iterator, and calling list.remove() directly increments modCount without updating the iterator's expectedModCount.
// File: CMEScenario1Demo.java
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.List;
public class CMEScenario1Demo {
public static void main(String[] args) {
List<String> statuses = new ArrayList<>();
statuses.add("PENDING"); statuses.add("DELIVERED");
statuses.add("PENDING"); statuses.add("RETURNED");
statuses.add("PENDING");
System.out.println("=== CME from list.remove() inside for-each ===");
try {
for (String status : statuses) {
System.out.println(" Visiting: " + status);
if ("PENDING".equals(status)) {
statuses.remove(status); // modCount++ but iterator.expectedModCount unchanged
}
}
} catch (ConcurrentModificationException e) {
System.out.println(" CME fired after modification detected");
}
System.out.println(" List after partial modification: " + statuses);
// Partial state: first PENDING was removed, then CME fired
System.out.println();
// CME from add() inside for-each
List<String> tags = new ArrayList<>(List.of("java", "spring", "kafka"));
System.out.println("=== CME from list.add() inside for-each ===");
try {
for (String tag : tags) {
System.out.println(" Visiting: " + tag);
if ("spring".equals(tag)) {
tags.add("hibernate"); // structural modification during traversal
}
}
} catch (ConcurrentModificationException e) {
System.out.println(" CME fired after add()");
}
System.out.println(" Tags after partial modification: " + tags);
}
}Output:
=== CME from list.remove() inside for-each ===
Visiting: PENDING
Visiting: DELIVERED
CME fired after modification detected
List after partial modification: [DELIVERED, PENDING, RETURNED, PENDING]
=== CME from list.add() inside for-each ===
Visiting: java
Visiting: spring
CME fired after add()
Tags after partial modification: [java, spring, kafka, hibernate]
Scenario 2 — External Modification Between Iterator Calls
CME does not require a for-each loop. Any structural modification to a collection after an iterator is created but before the iterator is exhausted will trigger it.
// File: CMEScenario2Demo.java
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public class CMEScenario2Demo {
public static void main(String[] args) {
// CME between iterator creation and use
List<Integer> prices = new ArrayList<>(List.of(100, 200, 300, 400, 500));
Iterator<Integer> it = prices.iterator();
it.next(); // returns 100 — OK so far
prices.add(600); // structural change after iterator was created
System.out.println("=== CME from modification between next() calls ===");
try {
it.next(); // modCount mismatch — throws CME
} catch (ConcurrentModificationException e) {
System.out.println(" CME fired — modCount changed between next() calls");
}
System.out.println();
// CME during Map iteration — modifying the map's keySet
Map<String, Integer> inventory = new HashMap<>();
inventory.put("Laptop", 10); inventory.put("Mouse", 50);
inventory.put("Keyboard", 25); inventory.put("Monitor", 8);
System.out.println("=== CME from map.put() during entrySet iteration ===");
try {
for (Map.Entry<String, Integer> entry : inventory.entrySet()) {
System.out.println(" Processing: " + entry.getKey());
if ("Mouse".equals(entry.getKey())) {
inventory.put("Webcam", 15); // structural change during iteration
}
}
} catch (ConcurrentModificationException e) {
System.out.println(" CME fired during Map iteration");
}
System.out.println(" Inventory now: " + inventory);
}
}Output:
=== CME from modification between next() calls ===
CME fired — modCount changed between next() calls
=== CME from map.put() during entrySet iteration ===
Processing: Laptop
Processing: Mouse
CME fired during Map iteration
Inventory now: {Laptop=10, Mouse=50, Keyboard=25, Monitor=8, Webcam=15}
Every Safe Fix Pattern
Fix 1 — iterator.remove() for Removal During Traversal
The iterator's own remove() method is the only structurally safe removal during traversal. It removes the last element returned by next() and updates expectedModCount to match the new modCount internally.
// File: CMEFix1IteratorRemove.java
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class CMEFix1IteratorRemove {
public static void main(String[] args) {
List<String> orders = new ArrayList<>(
List.of("ORD-001:PENDING", "ORD-002:DELIVERED", "ORD-003:PENDING",
"ORD-004:CANCELLED", "ORD-005:PENDING")
);
System.out.println("Before: " + orders);
// iterator.remove() — safe because it syncs expectedModCount after removal
Iterator<String> it = orders.iterator();
while (it.hasNext()) {
String order = it.next();
if (order.contains("PENDING")) {
it.remove(); // removes last returned element — modCount sync happens here
}
}
System.out.println("After removing PENDING (iterator.remove): " + orders);
}
}Output:
Before: [ORD-001:PENDING, ORD-002:DELIVERED, ORD-003:PENDING, ORD-004:CANCELLED, ORD-005:PENDING]
After removing PENDING (iterator.remove): [ORD-002:DELIVERED, ORD-004:CANCELLED]
Fix 2 — removeIf() for Predicate-Based Removal (Java 8+)
Collection.removeIf(predicate) is the cleanest modern approach. It handles modCount bookkeeping internally, eliminates the explicit iterator boilerplate, and expresses intent clearly.
// File: CMEFix2RemoveIf.java
import java.util.ArrayList;
import java.util.List;
public class CMEFix2RemoveIf {
public static void main(String[] args) {
List<Integer> scores = new ArrayList<>(List.of(88, 42, 95, 15, 76, 33, 91, 28));
System.out.println("Before: " + scores);
// removeIf — no explicit iterator, no CME risk
scores.removeIf(score -> score < 50);
System.out.println("After removeIf (< 50): " + scores);
// Works on Map views too
java.util.Map<String, Integer> stock = new java.util.HashMap<>();
stock.put("Laptop", 5); stock.put("Mouse", 0); stock.put("Keyboard", 12);
stock.put("Monitor", 0); stock.put("Webcam", 3);
System.out.println("\nStock before: " + stock);
stock.entrySet().removeIf(entry -> entry.getValue() == 0); // remove out-of-stock
System.out.println("Stock after removing zero-qty: " + stock);
}
}Output:
Before: [88, 42, 95, 15, 76, 33, 91, 28]
After removeIf (< 50): [88, 95, 76, 91]
Stock before: {Laptop=5, Keyboard=12, Monitor=0, Mouse=0, Webcam=3}
Stock after removing zero-qty: {Laptop=5, Keyboard=12, Webcam=3}
Fix 3 — Collect First, Modify After
When both adding and removing during traversal is needed, or when the removal logic is complex, collect the items to change in a separate list, finish the traversal, then apply all changes.
// File: CMEFix3CollectThenModify.java
import java.util.ArrayList;
import java.util.List;
public class CMEFix3CollectThenModify {
public static void main(String[] args) {
List<String> products = new ArrayList<>(
List.of("Laptop-5G", "Mouse-Wireless", "Keyboard-Basic",
"Monitor-4K", "Laptop-i5", "Webcam-HD")
);
// Collect items to remove and items to add — then apply outside the loop
List<String> toRemove = new ArrayList<>();
List<String> toAdd = new ArrayList<>();
for (String product : products) {
if (product.startsWith("Laptop")) {
toRemove.add(product); // collect for removal
toAdd.add(product.replace("Laptop", "MacBook")); // collect replacement
}
}
// Apply changes after traversal — no active iterator at this point
products.removeAll(toRemove);
products.addAll(toAdd);
System.out.println("After replacement: " + products);
}
}Output:
After replacement: [Mouse-Wireless, Keyboard-Basic, Monitor-4K, Webcam-HD, MacBook-5G, MacBook-i5]
Fix 4 — Standard Index Loop for ArrayList
For ArrayList, a descending index loop avoids CME entirely because it does not use an iterator — it uses direct array index access. Traversing backward means index shifts caused by removal do not affect elements you have not yet visited.
// File: CMEFix4IndexLoop.java
import java.util.ArrayList;
import java.util.List;
public class CMEFix4IndexLoop {
public static void main(String[] args) {
List<String> notifications = new ArrayList<>(
List.of("INFO: Login", "ERROR: DB Timeout", "INFO: Logout",
"ERROR: NPE in OrderService", "WARN: High memory", "ERROR: 500")
);
System.out.println("Before: " + notifications);
// Descending index loop — no iterator, no modCount, no CME
// Descending ensures indices of unvisited elements (lower indices) are stable
for (int i = notifications.size() - 1; i >= 0; i--) {
if (notifications.get(i).startsWith("ERROR")) {
notifications.remove(i); // shifts elements at higher indices (already visited)
}
}
System.out.println("After removing ERROR logs: " + notifications);
System.out.println();
// Ascending index loop is risky — skips elements after removal
List<Integer> values = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6));
for (int i = 0; i < values.size(); i++) {
if (values.get(i) % 2 == 0) {
values.remove(i); // index i shifts — next element at i is skipped
i--; // compensate by stepping back — necessary fix
}
}
System.out.println("After ascending loop with i-- compensation: " + values);
}
}Output:
Before: [INFO: Login, ERROR: DB Timeout, INFO: Logout, ERROR: NPE in OrderService, WARN: High memory, ERROR: 500]
After removing ERROR logs: [INFO: Login, INFO: Logout, WARN: High memory]
After ascending loop with i-- compensation: [1, 3, 5]
Fix 5 — Use Concurrent Collections for Multi-threaded Code
When multiple threads access the same collection and at least one writes, the fix is not an iteration pattern — it is choosing the right collection from java.util.concurrent.
// File: CMEFix5ConcurrentCollections.java
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
public class CMEFix5ConcurrentCollections {
public static void main(String[] args) {
// CopyOnWriteArrayList — fail-safe for read-heavy, write-rare lists
CopyOnWriteArrayList<String> listeners = new CopyOnWriteArrayList<>();
listeners.add("EmailService"); listeners.add("SMSService"); listeners.add("PushService");
// No CME — each write copies the array, iterator works on original snapshot
for (String listener : listeners) {
System.out.println(" Notifying: " + listener);
if ("SMSService".equals(listener)) {
listeners.remove("PushService"); // modifies list during iteration — safe
}
}
System.out.println("Listeners after: " + listeners);
System.out.println();
// ConcurrentHashMap — fail-safe for concurrent Map access
ConcurrentHashMap<String, Integer> sessionCount = new ConcurrentHashMap<>();
sessionCount.put("Mumbai", 142); sessionCount.put("Delhi", 89);
sessionCount.put("Bengaluru", 211); sessionCount.put("Chennai", 63);
System.out.println("=== ConcurrentHashMap — no CME on structural modification ===");
for (Map.Entry<String, Integer> entry : sessionCount.entrySet()) {
System.out.println(" " + entry.getKey() + ": " + entry.getValue() + " sessions");
sessionCount.put("Hyderabad", 55); // structural change — no CME
}
System.out.println("Final map: " + sessionCount);
}
}Output:
Notifying: EmailService
Notifying: SMSService
Notifying: PushService
Listeners after: [EmailService, SMSService]
=== ConcurrentHashMap — no CME on structural modification ===
Mumbai: 142 sessions
Delhi: 89 sessions
Bengaluru: 211 sessions
Chennai: 63 sessions
Final map: {Mumbai=142, Delhi=89, Bengaluru=211, Chennai=63, Hyderabad=55}
When ConcurrentModificationException Does NOT Fire
Understanding when CME does not fire is as important as knowing when it does.
CME DOES NOT FIRE when: 1. Using fail-safe collections: CopyOnWriteArrayList, CopyOnWriteArraySet ConcurrentHashMap, ConcurrentSkipListMap, ConcurrentLinkedQueue — these never throw CME 2. Using iterator.remove() or iterator.add() (ListIterator): — the iterator syncs expectedModCount after its own structural changes 3. Using removeIf(), replaceAll(), sort() on the list directly (not during an external iteration): — these handle modCount internally 4. Calling List.set(index, value) during iteration: — set() is not structural, does not change size, does not increment modCount 5. Reading operations: get(), contains(), size(), isEmpty() — read-only, no structural change 6. Iterating a copy of the collection: for (String item : new ArrayList<>(originalList)): — iterator is on the copy; modifications to original are invisible
Basic Example
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>(List.of(10, 15, 20, 25, 30));
// WRONG — removing directly during a for-each loop throws CME
try {
for (int n : numbers) {
if (n == 20) {
numbers.remove(Integer.valueOf(n));
}
}
} catch (ConcurrentModificationException e) {
System.out.println("Caught: " + e.getClass().getSimpleName());
}
System.out.println("List after failed removal: " + numbers);
// CORRECT — iterator.remove() keeps the iterator's state in sync
List<Integer> safeNumbers = new ArrayList<>(List.of(10, 15, 20, 25, 30));
Iterator<Integer> it = safeNumbers.iterator();
while (it.hasNext()) {
if (it.next() == 20) {
it.remove();
}
}
System.out.println("List after safe removal: " + safeNumbers);
}
}Output:
Caught: ConcurrentModificationException
List after failed removal: [10, 15, 25, 30]
List after safe removal: [10, 15, 25, 30]
How it works:
- ›The for-each loop compiles to an iterator. Calling
numbers.remove(...)on the list directly changes its size and increments its internalmodCount, but the iterator's own copy of that count is never updated. - ›The next time the iterator calls
next(), it notices the mismatch and throwsConcurrentModificationException— the20was already removed by then, leaving the list in a partially modified state. - ›
Iterator.remove()is the safe alternative: it removes the last element returned bynext()through the iterator itself, so the iterator immediately re-syncs its internal count and no exception is thrown. - ›This is why every safe removal-during-iteration pattern in Java either goes through
Iterator.remove()or a method likeremoveIf()that manages it internally.
Performance Considerations
The modCount check in next() is an int comparison — effectively free. It adds no measurable overhead to iteration. The performance question is not about CME detection — it is about which fix pattern to use.
| Fix Pattern | Time Complexity | Memory | Best For |
|---|---|---|---|
iterator.remove() | O(n) shift on ArrayList | O(1) extra | Single removal condition during traversal |
removeIf(predicate) | O(n) | O(1) extra | Simple predicate, Java 8+ |
| Collect then modify | O(n) traversal + O(k) changes | O(k) for temp list | Complex conditions, both add and remove |
| Descending index loop | O(n) | O(1) extra | ArrayList-specific, no iterator needed |
new ArrayList<>(original) | O(n) copy | O(n) for copy | Read-only iteration, modify original separately |
| Switch to concurrent collection | O(n) per write for COWAL | O(n) copy | Multi-threaded write-rare |
The collect then modify pattern is the most flexible but uses O(k) extra memory for k changes. For very large collections where k is large, iterator.remove() or removeIf() is more memory-efficient.
Best Practices
Treat CME as a programming error to fix, not an exception to catch. ConcurrentModificationException means the code structure is wrong — a collection is being modified during iteration in an unsafe way. Wrapping the iteration in try-catch(ConcurrentModificationException) and continuing leaves the collection in a partially modified state and hides the real bug. The only correct response is to fix the traversal pattern.
Always use removeIf() for simple removal conditions in Java 8+. One line, no iterator boilerplate, handles modCount internally, and clearly expresses intent. list.removeIf(item -> item.getStatus().equals("PENDING")) is always preferable to an explicit iterator loop for straightforward conditions.
Separate the decision to remove from the act of removing. For complex processing where you need to inspect an element fully before deciding — involving accumulated state from earlier elements — the collect-then-modify pattern is the right design. Decide during traversal, remove after. This is cleaner than forcing all logic into a removeIf predicate.
Never add elements to a collection directly inside a for-each loop on that collection. Adding elements while iterating a fail-fast collection always triggers CME. Use ListIterator.add() for in-place insertion during traversal on a List, or collect additions and addAll() after the loop.
Common Mistakes
Mistake 1 — Catching CME and Continuing
List<String> items = new ArrayList<>(List.of("A", "B", "C", "D", "E"));
// WRONG — catching CME hides the bug and leaves items in a partial state
try {
for (String item : items) {
if ("C".equals(item)) {
items.remove(item); // CME fires on next next() call
}
}
} catch (ConcurrentModificationException ignored) {
// Never swallow CME — data is in undefined state here
}
// items is now ["A", "B", "D", "E"] — "C" removed, but was anything else skipped?
// CORRECT — fix the pattern
items.removeIf("C"::equals);Mistake 2 — Removing by Value on a List of Integers Using the Wrong Overload
List<Integer> ids = new ArrayList<>(List.of(101, 102, 103, 104, 105));
// WRONG — remove(102) calls remove(int index) — removes element AT index 102
// which throws IndexOutOfBoundsException for small lists
// This is a separate bug, but it comes up alongside CME discussions
ids.remove(102); // IndexOutOfBoundsException — not CME, but confuses beginners
// CORRECT — remove by value must box the int
ids.remove(Integer.valueOf(102)); // removes the value 102
System.out.println(ids); // [101, 103, 104, 105]Mistake 3 — Assuming set() is Safe When It Should Not Be
List<String> names = new ArrayList<>(List.of("alice", "bob", "charlie"));
Iterator<String> it = names.iterator();
it.next(); // "alice"
names.set(0, "ALICE"); // set() does NOT increment modCount — still safe so far
it.next(); // "bob" — no CME here (set is not structural)
names.add("dave"); // structural — increments modCount
try {
it.next(); // CME fires — add() changed modCount
} catch (ConcurrentModificationException e) {
System.out.println("CME fired after add(), not after set()");
}Output:
CME fired after add(), not after set()
Mistake 4 — Confusing CME with Multi-threading When the Bug Is Single-threaded
// This is single-threaded — but still triggers CME
List<String> products = new ArrayList<>(List.of("A", "B", "C"));
for (String product : products) {
helper(products, product); // CME fires here — not because of threads
}
static void helper(List<String> list, String item) {
if ("B".equals(item)) {
list.remove(item); // modifies the list — same thread, same iterator, CME
}
}
// The fix is the same regardless of single or multi-threaded:
// use removeIf, iterator.remove(), or collect-then-modifyInterview Questions
Q1. What is ConcurrentModificationException in Java and when does it occur?
ConcurrentModificationException is an unchecked RuntimeException thrown by fail-fast iterators when they detect that the collection was structurally modified outside the iterator while the iterator was actively traversing it. It is detected via a modCount field — the collection increments modCount on every structural change, and the iterator checks whether its expectedModCount still matches on every next() call. It fires in single-threaded code just as readily as multi-threaded code — the most common cause is calling list.remove() or list.add() inside a for-each loop on the same list. It does not guarantee correctness if ignored — the collection is in a partially modified, potentially inconsistent state after CME fires.
Q2. Why is CME described as "best effort" in the Java specification?
The Java specification explicitly states that fail-fast iterators operate on a "best-effort basis" and the implementation does not guarantee that CME fires on every concurrent structural modification. This means: in practice, CME fires reliably for standard JDK implementations, but a program should never rely on CME detection to implement correct logic or thread safety. The spec permits implementations that do not track modCount or track it imprecisely. Code that swallows CME or uses its absence as a signal that no modification occurred is always incorrect.
Q3. What is the difference between iterator.remove() and list.remove() during iteration?
list.remove(element) increments the collection's modCount but does not update the iterator's expectedModCount. The next iterator.next() call detects the mismatch and throws CME. iterator.remove() calls the backing collection's removal method, which also increments modCount, but then immediately sets expectedModCount = modCount — keeping the iterator's snapshot in sync with the new state. This is why iterator.remove() is structurally safe: the iterator explicitly acknowledges the change it caused and updates itself accordingly.
Q4. Does List.set() trigger ConcurrentModificationException?
No. List.set(int index, E element) replaces an existing element without changing the collection's size. It does not increment modCount in standard implementations like ArrayList and LinkedList. The iterator's expectedModCount check passes because modCount was not changed. CME is only triggered by structural modifications — operations that add or remove elements and change the collection's size. set() is safe to call on a list while an iterator is active.
Q5. How does removeIf() avoid ConcurrentModificationException?
Collection.removeIf(predicate) internally iterates the collection and calls iterator.remove() for each matching element, then updates expectedModCount after each removal. Because it manages the iterator state internally, the iteration remains consistent. In Java 11+, ArrayList.removeIf() uses an optimised implementation that does not use an external iterator at all — it uses index-based scanning with a bitmap of elements to remove, then shifts elements in a single pass. Either way, the caller does not need to manage the iterator, and CME cannot be triggered by the removal logic.
Q6. What is the correct approach when you need to both remove and add elements during a single list traversal?
The safest pattern is to separate the decision from the modification: during traversal with an iterator, identify elements to remove (use iterator.remove()) and collect new elements in a separate temporary list. After the iterator is exhausted, call list.addAll(tempList) to apply the additions. list.addAll() after iteration is safe because no iterator is active at that point. Alternatively, ListIterator.add() can safely insert elements during traversal on a List. Direct list.add() inside a for-each loop on the same list always triggers CME.
FAQs
Can ConcurrentModificationException occur without using multiple threads?
Yes — and this is the most common misconception. The "concurrent" in the name refers to concurrent modification and iteration, not concurrent threads. Calling list.remove() inside a for-each loop in a completely single-threaded program triggers CME. No second thread is involved. The exception fires whenever the collection's modCount no longer matches the iterator's expectedModCount, regardless of how many threads are running.
Does Collections.synchronizedList() prevent ConcurrentModificationException?
No. Collections.synchronizedList() wraps a list with a mutex that serialises individual method calls, but iteration is not atomic — the lock is released between individual next() calls. If another thread calls add() or remove() on the wrapped list between two next() calls on the iterator, CME fires. To iterate safely, you must synchronise the entire iteration block: synchronized(syncList) { for (String s : syncList) {...} }. For iteration-safe concurrent access without manual synchronisation, use CopyOnWriteArrayList.
What happens to the collection state when CME fires?
The modification that caused CME has already completed successfully — the add() or remove() that incremented modCount ran to completion before CME was thrown. What is invalid is the iterator's state — it can no longer be used after CME. The collection itself is structurally sound and can be iterated again with a fresh iterator. The concern is the business logic: if the loop was processing elements and some were handled before CME fired while others were not, the processing is incomplete and the application state may be inconsistent.
Is CME thrown from Set and Map as well as List?
Yes. CME fires from HashSet, LinkedHashSet, TreeSet, HashMap, LinkedHashMap, and TreeMap iterators under the same conditions as ArrayList. Any structural modification to the collection while an iterator obtained from it is active triggers CME on the next next() call. For maps, structural modifications include put() on new keys, remove(), and clear(). Calling entry.setValue() via Map.Entry during entrySet() iteration does not increment modCount and is safe.
Does using a Stream avoid ConcurrentModificationException?
No — standard Collection.stream() creates a Spliterator backed by the collection. Modifying the backing collection while processing the stream can trigger CME or produce incorrect results. The stream source is the live collection. To safely process and modify, collect results to a new list and apply changes after the stream operation completes. CopyOnWriteArrayList.stream() is safe for concurrent modification.
What is the best way to handle ConcurrentModificationException in production code?
Do not catch it — fix the root cause. The correct responses are: use removeIf() for simple conditional removal, use iterator.remove() for removal during traversal, collect changes and apply after iteration for complex cases, or switch to a concurrent collection for multi-threaded scenarios. Logging CME and retrying the iteration is never a correct solution — the retry will trigger CME again under the same conditions unless the underlying pattern is fixed.
Summary
ConcurrentModificationException is a fail-fast iterator's immediate signal that a collection was structurally modified outside the iterator during traversal. It is detected via a modCount counter that every java.util collection maintains — the iterator snapshots modCount at creation and verifies it on every next() call. A mismatch means a structural change happened, and CME fires.
The word "concurrent" is misleading — single-threaded code triggers it just as easily when list.remove() or list.add() is called inside a for-each loop. The fix is never to catch and ignore it. The correct fix depends on the situation: removeIf() for simple predicate removal, iterator.remove() for conditional removal during traversal, collect-then-modify for complex scenarios, or a concurrent collection for multi-threaded access.
For interviews: know that CME can fire single-threaded, explain modCount and expectedModCount precisely, distinguish iterator.remove() from list.remove() and why one is safe while the other is not, and describe the collect-then-modify pattern for the case where both removal and addition are needed in one pass.
What to Read Next
Learn Java's most commonly used resizable list.
Practice Programs
Put this topic into practice — each program has full code, output and a step-by-step explanation you can run online.
Triggers ConcurrentModificationException by modifying a list directly while a for-each loop is iterating it.
Walks a list with an explicit Iterator and removes matching elements safely during iteration.