Java • Collections and Strings

Avoiding ConcurrentModificationException — Avoid ConcurrentModificationException in basic scenarios (conceptual)

Learn why Java throws this common error when you modify a collection while looping through it, and how to fix it.

Java programming emblem with a coffee cup, code braces, and terminal
Inside this lesson 10 sections 5 questions 7 figures

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

Java ships with basic collection types like Vector and Hashtable. These are thread-safe but slow and lack a unified design.

1998 • Java 1.2 — Collections Framework

The Java Collections Framework introduces ArrayList, HashMap, and the Iterator interface. Fail-fast iterators are built in, giving rise to ConcurrentModificationException.

2004 • Java 5 — Enhanced For-Loop

The for-each loop (for-each syntax) is introduced, making iteration simpler. However, it also hides the underlying iterator, making ConcurrentModificationException a frequent surprise for beginners.

2014 • Java 8 — removeIf & Streams

Java 8 adds the removeIf() method and the Streams API, providing clean, safe alternatives that avoid ConcurrentModificationException entirely.

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.

1

Structural Modification

Any operation that changes the size of a collection — such as add(), remove(), or clear() — is a structural modification. Simply updating a value at an existing index (e.g., set()) is NOT structural.
2

Fail-Fast Iterator

An iterator that immediately reports a problem by throwing ConcurrentModificationException rather than silently producing wrong results. Most standard collections (ArrayList, HashSet, HashMap) use fail-fast iterators.
3

The modCount Counter

An internal integer inside each collection that increments with every structural change. The iterator takes a snapshot of this value when it is created and checks it on every call to next().
4

Iterator.remove() — The Safe Path

The Iterator interface provides its own remove() method that deletes the last element returned by next() while keeping the iterator's internal state consistent. This is the classic safe way to remove elements during iteration.
✦

KEY TAKEAWAY

Think of a collection's iterator like a tour guide walking a group through a museum. The guide has a printed map of 10 rooms. If someone secretly adds or removes a room while the tour is underway, the map no longer matches reality, and the guide raises an alarm rather than leading the group into the wrong room. That alarm is the ConcurrentModificationException. The safe approach is to let the guide make changes to the tour plan directly, so the map stays up to date.

SECTION 3

Visual Explanation — How the Exception Triggers

How ConcurrentModificationException Is TriggeredArrayList contents:"A""B""C""D"modCount = 4Step 1: for-each loop starts → Iterator createdIterator snapshots expectedModCount = 4expectedModCount = 4Step 2: next() returns "A" ✓ — counts matchexpectedModCount (4) == modCount (4) → OK, return elementPASSStep 3: Loop body calls list.remove("B")Collection's modCount increments to 5modCount = 5 (changed!)expectedModCount = 4 (stale)Step 4: next() called again — MISMATCH!expectedModCount (4) ≠ modCount (5)FAILthrow ConcurrentModificationException
The diagram above shows the four-step sequence that triggers the exception. Notice how the collection's 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

if(expectedModCount≠modCount)→throwConcurrentModificationExceptionif (expectedModCount ≠ modCount) → throw ConcurrentModificationException

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

iterator.remove()→collection.modCount++;expectedModCount=modCount;iterator.remove() → { collection.modCount++; expectedModCount = modCount; }

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.

Decision Tree: Choosing a Safe Modification StrategyNeed to modify during iteration?NoYesUse normal for-each loop ✓What kind of modification?Remove onlyAdd or complexJava 8+?YesNoOption A: list.removeIf()Cleanest one-linerOption B: Iterator.remove()Classic approach, all Java versionsChoose a copy strategyOption C: Collect then modifyTwo-pass: iterate, then add/removeOption D: Copy list firstIterate over copy, modify original⚠ NEVER call list.add() or list.remove() directly inside a for-each or iterator loopThis ALWAYS risks ConcurrentModificationException
This decision tree guides you from your goal (modify a collection while iterating) to the appropriate safe strategy. Options A and B handle removal. Options C and D handle additions or more complex changes.
StrategyCode PatternBest ForJava Version
A. removeIf()list.removeIf(s -> s.length() < 3);Removing elements that match a conditionJava 8+
B. Iterator.remove()Iterator<String> it = list.iterator(); while (it.hasNext()) { if (...) it.remove(); }Removing the current element with full controlAll versions
C. Collect then modifyList<String> toRemove = new ArrayList<>(); for (String s : list) { if (...) toRemove.add(s); } list.removeAll(toRemove);Adding or removing elements; complex conditionsAll versions
D. Copy list firstfor (String s : new ArrayList<>(list)) { list.remove(s); }Simple cases where a shallow copy is acceptableAll versions

Four safe strategies for modifying a collection during iteration

💡

Important Note

The 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. 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 by for (int n : nums) { if (n % 2 == 0) nums.remove(Integer.valueOf(n)); }. This calls nums.remove() directly, which increments the collection's modCount while the hidden iterator's expectedModCount stays the same.
    Result: ConcurrentModificationException at runtime!
  2. 2

    Step 2 — Fix with Iterator.remove()

    Rewrite the loop using an explicit Iterator: Iterator<Integer> it = nums.iterator(); then while (it.hasNext()) { int n = it.next(); if (n % 2 == 0) it.remove(); }. Now it.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. 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);. The removeIf() method handles the iterator internally, so you never have to worry about modCount at all. The lambda expression n -> n % 2 == 0 is a predicate that returns true for even numbers.
    Result: nums is now [1, 3, 5] — clean and safe!
  4. 4

    Step 4 — Verify the Output

    Print the result with System.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.

CriteriaIterator.remove()removeIf()Collect-then-ModifyCopy List First
ReadabilityMedium — requires while loopHigh — single expressive lineMedium — two-pass logicHigh — familiar for-each
Supports Add?No — remove onlyNo — remove onlyYes — add after loopYes — modify original freely
Memory CostNone extraNone extraExtra list of items to removeFull copy of original list
Java Version1.2+8+1.2+1.2+
Risk of BugsLow if used correctlyVery lowLow — but more verbose codeLow — but uses more memory

Comparison of four safe modification strategies

✦

KEY TAKEAWAY

If you're using Java 8 or later and just need to remove elements, 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.

ConceptWhat You Learned (Single Thread)Advanced Version (Multi-Thread)
ProblemModifying a list inside a for-each loopTwo threads reading/writing the same list at the same time
DetectionFail-fast iterator checks modCountFail-fast detection is best-effort; not guaranteed across threads
SolutionIterator.remove(), removeIf(), collect-then-modifyThread-safe collections: CopyOnWriteArrayList, ConcurrentHashMap, synchronized wrappers
Iterator TypeFail-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

In your own words, explain why calling list.remove(element) inside a for-each loop throws a ConcurrentModificationException, even though only one thread is running.
Show answer
The for-each loop secretly creates an Iterator object. This iterator takes a snapshot of the collection's internal modification counter (modCount) when it is created. When you call list.remove() directly, the collection increments modCount, but the iterator's copy (expectedModCount) stays the same. On the next call to the iterator's next() method, it detects the mismatch between expectedModCount and modCount. Because these two values no longer match, it throws ConcurrentModificationException. The word "concurrent" here means the modification happened at the same time as iteration, not that multiple threads are involved.

Question 2: BASIC

Given the following code, will it throw a ConcurrentModificationException? Why or why not? 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
No, this code will NOT throw a ConcurrentModificationException. It uses the iterator's own remove() method (it.remove()), not the list's remove() method (colors.remove()). When iterator.remove() is called, it removes the element from the collection and then immediately updates its expectedModCount to match the collection's new modCount. Since both counters remain in sync, the next call to it.next() passes the fail-fast check, and the program runs correctly. The resulting list will be ["red", "green"].

Question 3: INTERMEDIATE

You have a 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
(a) Iterator approach: Create an explicit iterator with Iterator<Integer> it = set.iterator(); then use while (it.hasNext()) { if (it.next() > 25) it.remove(); }. This safely removes 30, 40, and 50 while keeping 10 and 20. (b) removeIf approach: Simply write set.removeIf(n -> n > 25); — this one-liner does the same thing. The lambda expression n -> n > 25 is a predicate that returns true for any value over 25, and removeIf handles the iteration and removal internally. Both approaches produce the set {10, 20}. Note that removeIf works on HashSet just like it does on ArrayList, because both implement the Collection interface.

Question 4: APPLIED

You are building a student roster application. You have an 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
Since you need to both remove and add elements, neither Iterator.remove() nor removeIf() alone is sufficient — they only support removal. The best strategy is the collect-then-modify pattern. First, iterate through the list and collect the names to remove: List<String> toRemove = new ArrayList<>(); for (String name : students) { if (name.startsWith("X")) toRemove.add(name); }. After the loop finishes (no longer iterating), modify the original list: students.removeAll(toRemove); and then for (String removed : toRemove) { students.add("NewStudent"); }. Alternatively, you could create a copy of the list (new ArrayList<>(students)) and iterate over the copy while freely modifying the original. The collect-then-modify pattern is generally preferred because it makes the logic explicit and doesn't require duplicating the entire list in memory.

Question 5: CRITICAL THINKING

Consider this tricky scenario: an ArrayList has exactly two elements, ["A", "B"]. A programmer writes a for-each loop that removes "A" when it is encountered: 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
This is a subtle and important edge case. When the iterator calls next() the first time, it returns "A" (at index 0). The loop body then calls list.remove("A"), which shifts "B" to index 0 and decrements the list's size from 2 to 1. Now, when the iterator checks hasNext(), it compares its internal cursor (which is at position 1) against the new size (1). Since cursor equals size, hasNext() returns false, and the loop ends without ever calling next() again. Because the modCount check only happens inside next(), and next() is never called a second time, the mismatch is never detected. The loop exits silently — but it never processed "B" at all! This is far more dangerous than getting an exception because your program appears to work correctly while silently skipping elements. This is exactly why relying on 'it seems to work' is unreliable, and why you should always use a proper safe pattern regardless of whether the exception appears.

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.

Varsity Tutors • Java • Avoiding ConcurrentModificationException