ConcurrentModificationException in Java: Causes and Fixes

  • Last Updated: August 22, 2026
  • By: javahandson
  • Series
img

ConcurrentModificationException in Java: Causes and Fixes

ConcurrentModificationException in Java explained simply. Learn why it happens, how the modCount check works, and five clean fixes with code examples.

1. Introduction

You wrote a simple loop that iterates through a list, removes a few items, and continues processing. At first glance, it seems fine. However, when you execute it, Java throws a ConcurrentModificationException without any warning or hint—just a stack trace. If this situation sounds familiar, you are not alone. This is one of the most common frustrations that new Java developers encounter.

The name sounds scary. It hints at threads and race conditions. But most of the time, there is no second thread at all. Please ensure you’re the only one touching the collection. The word “concurrent” here is a bit of a lie, and that trips people up.

In this guide, we will unpack what really causes this error. We will look at the loop that breaks, why it breaks, and the clean ways to fix it. You will also see how to avoid it in the first place. By the end, this error will feel boring instead of scary.

We will focus on practical aspects. Each point includes a small code sample that you can easily paste and run. No prior knowledge of threading or complex theory is necessary—just a list, a loop, and a bit of curiosity.

1.1. What You Need to Know First

To understand the topic, you only need a bit of background information. If you have written a for-each loop over a list, you’re already prepared. Having some knowledge about Iterators is helpful as well, but we will explain that part along the way.

  • How a for-each loop reads a list or set.
  • What it means to add or remove items from a collection.
  • A rough idea of what an Iterator is (we cover it below).

2. What Is ConcurrentModificationException?

ConcurrentModificationException is a runtime error. Java throws it when you change a collection while you are looping over it. The collection notices the change mid-walk and refuses to keep going.

It sits in the java.util package. It extends RuntimeException, so the compiler never forces you to catch it. That is why it slips past you at compile time and shows up only when the code runs.

The main issue often arises from modifying a collection while iterating. The collection needs to remain unchanged during reading, and when it doesn’t, it raises an error.

2.0. When Threads Really Are Involved

Threads can also cause issues. For example, if one thread loops over a list while another thread adds elements to it, the looping thread may detect changes in the count and produce an error.

This is a rare but real problem that can happen in web applications and background jobs that share a collection. The solution requires using a thread-safe collection, which we will explain later. For now, remember that this error can occur in two ways, and the single-threaded version is much more common.

2.1. A Loop That Breaks

Let us start with the classic mistake. This loop tries to remove one name while walking the list.

List<String> names = new ArrayList<>();
names.add("Ravi");
names.add("Meena");
names.add("Arjun");
 
for (String name : names) {
    if (name.equals("Meena")) {
        names.remove(name);   // boom
    }
}

Run this and you get a ConcurrentModificationException. The loop asked the list to remove an item while it was still reading it. The list caught the change and threw the error on the next step.

Interview Insight
Interviewers love to ask why this error says “concurrent” when only one thread is running. The answer: the name refers to changing (modifying) the collection concurrently with iteration, not to multiple threads. A single thread can trigger it easily.

3. Why Does It Happen?

To see the cause, you have to look under the hood of the for-each loop. It is not magic. It quietly uses an Iterator, and that Iterator keeps a small counter to guard itself.

3.1. The for-each Loop Hides an Iterator

A for-each loop is sugar. Behind the scenes, Java turns it into a while loop that uses an Iterator. So this clean loop:

for (String name : names) {
    System.out.println(name);
}

Becomes something close to this:

Iterator<String> it = names.iterator();
while (it.hasNext()) {
    String name = it.next();
    System.out.println(name);
}

Now you can see the iterator. Every for-each loop that goes through a collection uses an iterator. The check happens in that iterator.

3.2. The modCount Check

Collections like ArrayList keep a field called modCount. It stands for modification count. Every structural change bumps this number up. Adding an item raises it. Removing one raises it too.

When you call iterator(), the Iterator saves a copy of modCount. It calls this copy expectedModCount. Each time you call next(), the Iterator compares the two numbers.

  • If the list has not changed, both numbers match. The loop keeps going.
  • If you changed the list, modCount moved but expectedModCount did not. The two no longer match.
  • On a mismatch, the Iterator throws ConcurrentModificationException right away.

The crash is a safety feature. It prevents you from getting outdated or incorrect data. Instead of skipping items quietly or reading past the end, it fails immediately.

3.3. A Step-by-Step Trace of the Crash

Let us walk through the broken loop one step at a time. It helps to see the exact moment things go wrong. Take a list with three names: Ravi, Meena, and Arjun.

  • The loop calls iterator(). The Iterator saves expectedModCount as 3 (three adds happened).
  • First next() returns Ravi. modCount is still 3, so the check passes.
  • Ravi is not Meena, so nothing changes. The loop moves on.
  • Second next() returns Meena. The check still passes here.
  • Now names.remove(Meena) runs. This bumps modCount to 4. But expectedModCount is still 3.
  • The loop calls next() again to get Arjun. The Iterator sees 4 does not equal 3. It throws at once.

Notice the timing. The error does not fire on the remove line. It fires on the next call to next(). That is why the stack trace points at the loop and not at your remove. This small detail confuses a lot of beginners.

There’s a tricky situation to consider. If you remove the second-to-last item, the Iterator may stop checking before it finishes. This can make it seem like the removal works by chance. If you then try the same code on a longer list, it may break. That’s why you should never depend on it working reliably.

3.4. Fail-Fast Behavior

This design has a name. It is called fail-fast. A fail-fast iterator stops the moment it spots trouble. It does not wait, and it does not guess.

The upside is clear. You learn about the bug at once, near the line that caused it. A silent bug that corrupts data would be far worse to track down later.

One important note from the Javadoc is that fail-fast behavior is not guaranteed. It’s best to think of it as an effort, not a guarantee. You should not write code that relies on this exception to trigger. Instead, use it as a way to catch bugs, not as a tool for controlling program flow.

Interview Insight
Know the difference between fail-fast and fail-safe iterators. Fail-fast (ArrayList, HashMap) throws on modification during iteration. Fail-safe (CopyOnWriteArrayList, ConcurrentHashMap) iterates over a copy or snapshot, so it never throws but may miss recent changes.

3.5. Why This Design Is a Good Thing

At first, a crash feels harsh. Why not just let the loop keep going? Because the alternative is worse. A loop that quietly reads bad data is a nightmare to debug.

Consider the risks of not checking. When you remove items from the list, it changes its size. This can cause your loop to read beyond the end of the list or to skip over an item that it should process. These errors can go unnoticed and are hard to fix.

So the fail-fast check trades a loud crash for a quiet disaster. A loud crash tells you the line, the cause, and the moment. You fix it in minutes. A quiet data bug can hide for weeks and reach real users. The design picks the lesser pain on purpose.

4. How to Fix It

Now for the good part. There are several clean ways to remove items without the crash. Pick the one that fits your case. Each has a spot where it shines.

4.1. Use the Iterator’s Own remove() Method

This is the classic fix. Grab the Iterator yourself. Then call its remove() method instead of the list’s remove(). The Iterator updates its own counter, so the numbers stay in sync.

Iterator<String> it = names.iterator();
while (it.hasNext()) {
    String name = it.next();
    if (name.equals("Meena")) {
        it.remove();   // safe
    }
}

The trick is small but important. it.remove() bumps both modCount and expectedModCount together. Since they still match, the next call to next() is happy. No exception this time.

  • Works on any standard collection with an iterator.
  • Removes the current item, the one you just got from next().
  • You must call next() before remove(), or you get an IllegalStateException.

4.2. Use removeIf() (Java 8 and Later)

If you are using Java 8 or a newer version, the best choice is to use the removeIf() method. This method allows you to set a condition and it will remove all items that match that condition. It takes care of the looping for you, making it safe and easy to use.

names.removeIf(name -> name.equals("Meena"));

One line, no loop, no Iterator to manage. It reads almost like plain English. Remove every name that equals Meena. This is the modern way, and most teams prefer it.

Interview Insight
removeIf() is not just shorter, it is often faster on an ArrayList. It can shift elements in one pass instead of shifting the whole tail on each individual remove. Mention this and you show real depth beyond “it looks cleaner”.

4.3. Loop Over a Copy

Sometimes you want to keep the plain for-each loop. In that case, loop over a copy of the collection and change the original inside. The copy never changes during the walk, so no crash.

for (String name : new ArrayList<>(names)) {
    if (name.equals("Meena")) {
        names.remove(name);   // safe, we walk the copy
    }
}

You iterate the fresh copy but edit the real list. Since the copy stays still, its Iterator stays calm. The cost is a bit of extra memory for that copy, which is fine for small lists.

4.4. Collect First, Then Remove

One safe way to do this is to split the work into two steps. First, go through the list and write down the items you want to remove. Keep them in a separate list. Then, remove all of them at once after you finish going through the list.

List<String> toRemove = new ArrayList<>();
for (String name : names) {
    if (name.equals("Meena")) {
        toRemove.add(name);
    }
}
names.removeAll(toRemove);

The loop only reads. It never changes the list it is walking. All the removing happens later, outside the loop. This reads clearly and avoids the trap by design.

4.5. Use a Concurrent Collection

If you truly work across threads, the fixes above are not enough. You need a collection built for it. Classes in the java.util.concurrent package handle changes during iteration without throwing.

  • CopyOnWriteArrayList: great for lists read often and written rarely. Each write makes a fresh copy.
  • ConcurrentHashMap: a map you can safely read and update from many threads at once.
  • These use fail-safe iterators, so they never throw ConcurrentModificationException.
List<String> safe = new CopyOnWriteArrayList<>(names);
for (String name : safe) {
    if (name.equals("Meena")) {
        safe.remove(name);   // no exception
    }
}

Keep one thing in mind. These collections trade speed or memory for safety. Do not reach for them just to dodge the error in single-thread code. Use the Iterator or removeIf() there instead.

4.6. Adding Safely with a ListIterator

So far, we have removed items. But what if you need to add an item during the walk? A regular Iterator cannot add. You need a ListIterator instead. It is a more advanced version that only works with lists.

A ListIterator can move both ways and can insert items. Its add() method updates the counter the same way remove() does. So the loop stays safe.

ListIterator<String> it = names.listIterator();
while (it.hasNext()) {
    String name = it.next();
    if (name.equals("Ravi")) {
        it.add("Ravi Kumar");   // safe insert
    }
}

The new item lands right after the current one. And because add() bumps expectedModCount along with modCount, no exception shows up. This is the clean way to grow a list mid-loop.

4.7. Filter with Streams for a New List

You might not need to change the original list. Sometimes, you just want a new list that excludes unwanted items. In Java 8 and later, streams make this process simple and clear.

List<String> kept = names.stream()
    .filter(name -> !name.equals("Meena"))
    .collect(Collectors.toList());

This leaves the source list untouched. It builds a fresh list that keeps only what you want. No Iterator, no counter, no crash. Reach for this when you are fine with a brand new list rather than editing in place.

4.8. Which Fix Is Fastest?

For most code, speed does not matter here. Pick the fix that reads best. But on very large lists, the choice can matter, so it helps to know the shape of it.

  • Iterator.remove() on an ArrayList shifts the tail on every single remove. On a big list with many removes, that adds up.
  • removeIf() on an ArrayList is smarter. It can do the whole job in one pass and shift once at the end.
  • On a LinkedList, single removes are cheap, so Iterator.remove() stays fast there.
  • Loop-over-a-copy pays for the copy up front, then removes are plain list operations.

The honest takeaway: reach for removeIf() by default. It is clean and it happens to be efficient on the list you use most. Only measure and switch if a profiler tells you this loop is a real hotspot. Do not guess.

5. The Same Trap in Maps and Sets

This error affects not just lists but also maps and sets. Any collection that fails fast can cause it. The reason is always the same: you modified the collection while looping through it.

5.1. Removing from a HashMap While Looping

Try to remove an entry from a HashMap inside a for-each loop and you get the same crash. Here is the broken version.

Map<String, Integer> scores = new HashMap<>();
scores.put("Ravi", 90);
scores.put("Meena", 75);
 
for (String key : scores.keySet()) {
    if (scores.get(key) < 80) {
        scores.remove(key);   // throws
    }
}

The fix looks familiar. Use an Iterator over the entry set, or use removeIf() on the key set. Both keep the counters in step.

Iterator<Map.Entry<String, Integer>> it = scores.entrySet().iterator();
while (it.hasNext()) {
    Map.Entry<String, Integer> entry = it.next();
    if (entry.getValue() < 80) {
        it.remove();   // safe
    }
}

5.2. The Same Idea for Sets

A HashSet works like a list in this case. If you try to remove an item while going through it, an error will occur. This happens because of the modCount guard we mentioned earlier.

Set<String> tags = new HashSet<>(Set.of("java", "old", "spring"));
 
// broken:
for (String tag : tags) {
    if (tag.equals("old")) {
        tags.remove(tag);   // throws
    }
}
 
// fixed:
tags.removeIf(tag -> tag.equals("old"));

Reach for the Iterator’s remove() or removeIf() and the problem goes away. This pattern carries across the whole Collections Framework. Once you learn it on a list, you know it for sets and maps too.

5.3. One Gotcha with keySet and values

A map has three views: the key set, the value collection, and the entry set. You can remove through the key set and the entry set. But some views do not support removal at all.

The safest way to handle a map is by using the entry set Iterator. This tool allows you to read both the key and the value, and you can remove the entire entry in one simple action. By using this method, cleaning up the map remains straightforward.

6. How to Avoid It in the First Place

Fixing the error is good. Not writing it is better. A few small habits keep this exception out of your code for good.

6.1. Simple Rules to Follow

  • Never call collection.remove() inside a for-each loop over that same collection.
  • Reach for removeIf() first when you are on Java 8 or later. It is the shortest safe path.
  • When removeIf() does not fit, grab the Iterator and use its remove() method.
  • Only pick a concurrent collection when real threads are involved, not to silence the error.
  • If you just need to read, never edit the collection inside the loop at all.

6.2. Pick the Right Fix

Each fix has a sweet spot. This quick table helps you choose without overthinking it.

SituationBest Fix
Single thread, remove by condition, Java 8+removeIf()
Single thread, older Java, need fine controlIterator.remove()
Want to keep the plain for-each loopLoop over a copy
Complex logic, remove after readingCollect first, then removeAll()
Real multi-threaded accessCopyOnWriteArrayList / ConcurrentHashMap

7. Common Mistakes People Make

Even after learning the fixes, a few slips still catch people. Watch out for these.

7.1. Catching the Exception Instead of Fixing It

Some people catch errors in a loop using try-catch to ignore them. This approach hides the problem but does not fix it. Your loop may still skip items or not complete its task. Instead of ignoring the issue, take the time to fix the loop.

7.2. Adding Items During Iteration

Removal gets all the attention, but adding breaks it too. Call list.add() inside a for-each loop and you get the same crash. Any structural change during iteration is the problem, not just removal.

7.3. Assuming It Means Threads

The word concurrent fools people into hunting for a threading bug. Most of the time there is none. Check your loop first. A single-thread remove is the usual culprit by far.

Interview Insight
A sharp follow-up: does a normal for loop with an index avoid the error? Yes, because it does not use an Iterator, so there is no modCount check. But you must adjust the index after each remove, or you will skip elements. It sidesteps the exception but opens a different bug.

8. A Real-World Example

Textbook lists of names can help you learn, but errors have a bigger impact when you’re working with actual code. Let’s examine a situation you might encounter in your job.

8.1. Cleaning Up a Shopping Cart

Imagine you have an online shopping cart. As you check the items, you remove any that are out of stock. The simple way to do this looks clean and is easy to read, but it fails when you try to remove an item.

List<CartItem> cart = getCartItems();
 
for (CartItem item : cart) {
    if (!item.isInStock()) {
        cart.remove(item);   // throws on the next item
    }
}

In a demo with two items, this might slip by. In production with a full cart, it crashes and a user sees an error page. That gap between the demo and real life is exactly where this bug hides.

8.2. The Clean Version

The fix is one line with removeIf(). It says what it means and never throws. Your future self will thank you for it.

cart.removeIf(item -> !item.isInStock());

Short, clear, and safe. It reads like a plain sentence. Remove every item that is not in stock. This is the kind of code that survives a busy Friday deploy without waking anyone up.

9. Interview Questions on ConcurrentModificationException

Q: Does ConcurrentModificationException always mean a threading problem?

A: No. Most of the time a single thread causes it. The error fires when you change a collection while a for-each loop walks over it. The word “concurrent” refers to modifying the collection at the same time as iterating, not to multiple threads.

Q: What is the easiest way to fix it?

A: On Java 8 or later, use removeIf(). One line like list.removeIf(x -> condition) removes matching items safely and never throws. For older Java, use the Iterator’s own remove() method instead of the collection’s remove().

Q: Why does the error fire on next() and not on the remove() line?

A: The remove() call bumps modCount but does not throw. The Iterator only compares modCount with expectedModCount on the next call to next(). That is where it spots the mismatch and throws, which is why the stack trace points at the loop.

Q: What is the difference between fail-fast and fail-safe iterators?

A: Fail-fast iterators (ArrayList, HashMap) throw ConcurrentModificationException the moment they detect a change during iteration. Fail-safe iterators (CopyOnWriteArrayList, ConcurrentHashMap) work on a copy or snapshot, so they never throw but may not reflect the most recent changes.

Q: Can a plain indexed for loop avoid the exception?

A: Yes, because it does not use an Iterator, so there is no modCount check. But it opens a different bug: after you remove an element, the indexes shift, so you must adjust the loop index or you will skip items. It sidesteps the exception rather than solving the real problem cleanly.

Q: Does adding items during iteration also cause it?

A: Yes. Any structural change during iteration triggers it, not just removal. If you need to add during a loop over a list, use a ListIterator and its add() method, which updates the counter and keeps the loop safe.

10. Conclusion

So there it is. ConcurrentModificationException in Java is not about threads most of the time. It fires when you change a collection while a for-each loop walks over it. The Iterator spots the change through its modCount check and fails fast to protect you.

The fixes are simple once you know them. Use removeIf() on modern Java. Use the Iterator’s own remove() for finer control. Loop over a copy when you want to keep the plain loop. Reach for concurrent collections only when real threads are in play.

Next time this error pops up, do not panic. Look at your loop, spot the change happening mid-walk, and swap in one of these fixes. What felt like a scary crash turns into a quick, boring fix.

Further Reading

 

Leave a Comment