Java Tutorial
🔍

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:

  1. ›The for-each loop compiles to an iterator. Calling numbers.remove(...) on the list directly changes its size and increments its internal modCount, but the iterator's own copy of that count is never updated.
  2. ›The next time the iterator calls next(), it notices the mismatch and throws ConcurrentModificationException — the 20 was already removed by then, leaving the list in a partially modified state.
  3. ›Iterator.remove() is the safe alternative: it removes the last element returned by next() through the iterator itself, so the iterator immediately re-syncs its internal count and no exception is thrown.
  4. ›This is why every safe removal-during-iteration pattern in Java either goes through Iterator.remove() or a method like removeIf() 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 PatternTime ComplexityMemoryBest For
iterator.remove()O(n) shift on ArrayListO(1) extraSingle removal condition during traversal
removeIf(predicate)O(n)O(1) extraSimple predicate, Java 8+
Collect then modifyO(n) traversal + O(k) changesO(k) for temp listComplex conditions, both add and remove
Descending index loopO(n)O(1) extraArrayList-specific, no iterator needed
new ArrayList<>(original)O(n) copyO(n) for copyRead-only iteration, modify original separately
Switch to concurrent collectionO(n) per write for COWALO(n) copyMulti-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-modify

Interview 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

Practice Programs

Put this topic into practice — each program has full code, output and a step-by-step explanation you can run online.

Browse all Java programs →