SECTION 1
Historical Context & Motivation
When Java was first released in the mid-1990s, its original collection classes like Vector and Hashtable were designed to be safe for multiple threads, but they were slow and clunky. Developers needed a better, faster way to work with groups of objects. In Java 1.2, Sun Microsystems introduced the Java Collections Framework, a unified set of interfaces and classes like ArrayList, HashMap, and HashSet. These new classes were much faster, but they introduced a new challenge: what should happen if your code tries to change a list while a loop is still reading through it?
The designers decided on a fail-fast strategy: if the collection notices it has been structurally changed during iteration, it immediately throws a ConcurrentModificationException. The word "concurrent" here does not necessarily mean multiple threads — it simply means "at the same time." Even a single-threaded program can trigger this error by modifying a collection while iterating over it. This design choice helps you catch bugs early rather than letting corrupted data silently slip through your program.
1995 • Java 1.0 Released
1998 • Java 1.2 — Collections Framework
2004 • Java 5 — Enhanced For-Loop
2014 • Java 8 — removeIf & Streams
The core question this lesson addresses is straightforward: why does Java prevent you from changing a collection while looping through it, and what safe techniques can you use instead? Understanding this will save you from one of the most common runtime errors Java programmers encounter.
SECTION 2
Core Principles & Definitions
Before we look at fixes, you need to understand the three key ideas behind this exception. When you use a for-each loop or explicitly call iterator() on a collection, Java creates an Iterator object that walks through the elements one at a time. That iterator keeps an internal counter called a modification count (often written as modCount in the source code). Every time the collection is structurally modified — an element is added, removed, or the size changes — this counter increments. When the iterator advances to the next element, it compares its own snapshot of modCount against the collection's current modCount. If they don't match, the iterator knows something changed behind its back, and it throws the exception.
Structural Modification
Fail-Fast Iterator
The modCount Counter
Iterator.remove() — The Safe Path
KEY TAKEAWAY
SECTION 3
Visual Explanation — How the Exception Triggers
modCount increments when you call list.remove() directly, while the iterator's expectedModCount stays frozen at the old value. On the very next call to next(), the mismatch is detected and the exception is thrown.The key insight from the diagram is that the check happens lazily — only when next() is called, not at the moment of modification. This means if your removal happens on the very last element and no further next() call occurs, you might not see the exception at all. That inconsistency is exactly why relying on "it works sometimes" is dangerous — your code should always use a safe removal pattern.
SECTION 4
How It Works Under the Hood
The modCount Mechanism
Inside the source code of ArrayList (and most other standard collections), there is a protected integer field called modCount. Every time add(), remove(), or clear() is called, modCount++ happens internally. When you request an iterator (which is what the for-each loop does behind the scenes), the iterator copies this value into its own variable, typically called expectedModCount. The check logic inside the iterator's next() method is conceptually this simple:
FAIL-FAST CHECK
This comparison runs at the start of every next() call and every remove() call on the iterator.
Why Iterator.remove() Is Safe
When you call iterator.remove() instead of list.remove(), the iterator performs two actions: it removes the element from the underlying collection (which increments modCount), and then it immediately updates its own expectedModCount to match. Since both counters are back in sync, the next call to next() sees no mismatch, and no exception is thrown.
SAFE REMOVAL
Both counters are synchronized after the removal, so the fail-fast check passes on the next next() call.
The For-Each Trap
The enhanced for-each loop (for (String s : list)) is syntactic sugar — it creates an iterator behind the scenes, but you have no direct access to that iterator. This means you cannot call iterator.remove() from inside a for-each loop. If you need to remove elements while iterating, you must use an explicit iterator loop or one of the alternative strategies we will cover in the next sections.
SECTION 5
Safe Strategies for Modifying Collections During Iteration
There are several well-established patterns for avoiding ConcurrentModificationException. Each strategy has trade-offs in terms of readability, performance, and which Java version supports it. The diagram below illustrates the decision tree, and the table that follows gives you a quick reference.
| Strategy | Code Pattern | Best For | Java Version |
|---|---|---|---|
| A. removeIf() | list.removeIf(s -> s.length() < 3); | Removing elements that match a condition | Java 8+ |
| B. Iterator.remove() | Iterator<String> it = list.iterator(); while (it.hasNext()) { if (...) it.remove(); } | Removing the current element with full control | All versions |
| C. Collect then modify | List<String> toRemove = new ArrayList<>(); for (String s : list) { if (...) toRemove.add(s); } list.removeAll(toRemove); | Adding or removing elements; complex conditions | All versions |
| D. Copy list first | for (String s : new ArrayList<>(list)) { list.remove(s); } | Simple cases where a shallow copy is acceptable | All versions |
Four safe strategies for modifying a collection during iteration
Important Note
set() method on an ArrayList (which replaces an element at a given index without changing the size) is not a structural modification. You can safely call list.set(index, newValue) inside a traditional index-based for loop without triggering the exception. The key rule is: if the size of the collection changes, it's structural; if only the contents change, it's not.SECTION 6
Worked Example — Removing Even Numbers from a List
Let's walk through a complete example. Suppose you have a list of integers and you want to remove all even numbers. We will first show the broken version, explain why it fails, and then rewrite it using two safe strategies.
Removing Even Numbers Safely
- 1
Step 1 — The Broken Code (DON'T do this)
Here is the code many beginners write:List<Integer> nums = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6));followed byfor (int n : nums) { if (n % 2 == 0) nums.remove(Integer.valueOf(n)); }. This callsnums.remove()directly, which increments the collection's modCount while the hidden iterator's expectedModCount stays the same.Result: ConcurrentModificationException at runtime! - 2
Step 2 — Fix with Iterator.remove()
Rewrite the loop using an explicit Iterator:Iterator<Integer> it = nums.iterator();thenwhile (it.hasNext()) { int n = it.next(); if (n % 2 == 0) it.remove(); }. Nowit.remove()updates both the collection's modCount and the iterator's expectedModCount, keeping them in sync.Result: nums is now [1, 3, 5] — no exception! - 3
Step 3 — Fix with removeIf() (Java 8+)
The simplest approach with Java 8 or later is a single line:nums.removeIf(n -> n % 2 == 0);. TheremoveIf()method handles the iterator internally, so you never have to worry about modCount at all. The lambda expressionn -> n % 2 == 0is a predicate that returns true for even numbers.Result: nums is now [1, 3, 5] — clean and safe! - 4
Step 4 — Verify the Output
Print the result withSystem.out.println(nums);to confirm. Both safe methods produce[1, 3, 5]. The key lesson is that the logic of "what to remove" is the same in all three versions — only the mechanism for how the removal happens differs.Output: [1, 3, 5]
SECTION 7
Strengths and Limitations of Each Approach
No single strategy is perfect for every situation. Your choice depends on the Java version available, whether you need to add or only remove elements, and how readable you want your code to be. The table below compares the four strategies on several criteria.
| Criteria | Iterator.remove() | removeIf() | Collect-then-Modify | Copy List First |
|---|---|---|---|---|
| Readability | Medium — requires while loop | High — single expressive line | Medium — two-pass logic | High — familiar for-each |
| Supports Add? | No — remove only | No — remove only | Yes — add after loop | Yes — modify original freely |
| Memory Cost | None extra | None extra | Extra list of items to remove | Full copy of original list |
| Java Version | 1.2+ | 8+ | 1.2+ | 1.2+ |
| Risk of Bugs | Low if used correctly | Very low | Low — but more verbose code | Low — but uses more memory |
Comparison of four safe modification strategies
KEY TAKEAWAY
removeIf() is almost always your best bet — it's concise, safe, and expressive. Think of it like using a filter on a photo: you define what you want gone, and the method handles the messy details of actually removing it. For older Java versions or when you need to add elements, use Iterator.remove() or the collect-then-modify pattern.SECTION 8
Connection to Advanced Concurrency Concepts
Everything we've discussed so far happens in a single thread — one sequence of instructions running top to bottom. But the name "ConcurrentModificationException" hints at a deeper world: multithreading. In more advanced Java programs, multiple threads may access the same collection simultaneously. The fail-fast mechanism we studied still applies in that scenario, but solving it requires more powerful tools.
| Concept | What You Learned (Single Thread) | Advanced Version (Multi-Thread) |
|---|---|---|
| Problem | Modifying a list inside a for-each loop | Two threads reading/writing the same list at the same time |
| Detection | Fail-fast iterator checks modCount | Fail-fast detection is best-effort; not guaranteed across threads |
| Solution | Iterator.remove(), removeIf(), collect-then-modify | Thread-safe collections: CopyOnWriteArrayList, ConcurrentHashMap, synchronized wrappers |
| Iterator Type | Fail-fast (throws exception immediately) | Fail-safe / weakly consistent (never throws, may show stale data) |
Single-thread vs. multi-thread approaches to concurrent modification
The java.util.concurrent package provides collections like CopyOnWriteArrayList and ConcurrentHashMap that use different internal strategies (like copying the entire array on every write, or locking individual segments) to allow safe concurrent access. You don't need to master these yet, but knowing they exist gives you a roadmap for future learning. The single-threaded patterns you learned today are the foundation for understanding those more advanced tools.
SECTION 9
Practice Problems
Question 1: CONCEPTUAL
list.remove(element) inside a for-each loop throws a ConcurrentModificationException, even though only one thread is running.Show answer
Question 2: BASIC
List<String> colors = new ArrayList<>(Arrays.asList("red", "blue", "green"));
Iterator<String> it = colors.iterator();
while (it.hasNext()) { String c = it.next(); if (c.equals("blue")) it.remove(); }Show answer
Question 3: INTERMEDIATE
HashSet<Integer> containing the values {10, 20, 30, 40, 50}. You want to remove all values greater than 25. Write code that accomplishes this using two different safe strategies: (a) the Iterator approach and (b) the removeIf() approach.Show answer
Question 4: APPLIED
ArrayList<String> of student names, and you need to: (1) remove any student whose name starts with "X" (test accounts), and (2) add the name "NewStudent" to the list for every removed account. Since you need both additions and removals, explain which safe strategy you should use and outline the code structure.Show answer
Question 5: CRITICAL THINKING
for (String s : list) { if (s.equals("A")) list.remove(s); }. Surprisingly, this code does NOT always throw ConcurrentModificationException in some JVM implementations. Explain why it might "work" silently and why this is actually more dangerous than getting the exception.Show answer
SUMMARY
Lesson Summary
Summary
A ConcurrentModificationException occurs when you structurally modify a collection (add, remove, or clear) while a fail-fast iterator is still active. The iterator detects the problem by comparing its internal expectedModCount against the collection's modCount every time next() is called. If they don't match, the exception is thrown immediately.
To avoid this exception, use one of four safe strategies: Iterator.remove() for classic removal during iteration, removeIf() for clean one-liner removals in Java 8+, the collect-then-modify pattern when you need additions, or iterating over a copy of the collection. Never call list.remove() or list.add() directly inside a for-each loop or while an iterator is active. Understanding this fail-fast mechanism is the foundation for working safely with Java collections and prepares you for the thread-safe concurrent collections you will encounter in more advanced courses.