ConcurrentModificationException in Java: Causes and Fixes
-
Last Updated: August 22, 2026
-
By: javahandson
-
Series
ConcurrentModificationException in Java explained simply. Learn why it happens, how the modCount check works, and five clean fixes with code examples.
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.
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.
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.
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.
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. |
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.
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.
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.
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.
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.
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.
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. |
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.
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.
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.
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”. |
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
}
}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.
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.
Fixing the error is good. Not writing it is better. A few small habits keep this exception out of your code for good.
Each fix has a sweet spot. This quick table helps you choose without overthinking it.
| Situation | Best Fix |
|---|---|
| Single thread, remove by condition, Java 8+ | removeIf() |
| Single thread, older Java, need fine control | Iterator.remove() |
| Want to keep the plain for-each loop | Loop over a copy |
| Complex logic, remove after reading | Collect first, then removeAll() |
| Real multi-threaded access | CopyOnWriteArrayList / ConcurrentHashMap |
Even after learning the fixes, a few slips still catch people. Watch out for these.
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.
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.
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. |
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.
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.
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.
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.
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().
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.
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.
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.
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.
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.