We’ve all been there: you’re looping through a list, you find something you want to delete, you call list.remove(), and boom—ConcurrentModificationException.
It’s one of those errors that feels personal. You think, “I’m the only thread here, what do you mean concurrent modification?”
The Quick Fix
If you just want to remove items from a list while looping, don’t use a for-each loop. Use an Iterator explicitly:
// ❌ THIS CRASHES
for (String item : items) {
if (shouldRemove(item)) {
items.remove(item); // ConcurrentModificationException!
}
}
// ✅ DO THIS INSTEAD
Iterator<String> it = items.iterator();
while (it.hasNext()) {
if (shouldRemove(it.next())) {
it.remove(); // Safe!
}
}
// ✅ OR USE JAVA 8+ (PREFERED)
items.removeIf(item -> shouldRemove(item));
Why Does This Happen?
Despite the name, ConcurrentModificationException usually has nothing to do with multiple threads. In most cases, it’s just Java’s way of telling you that you’re trying to change a collection while you’re in the middle of reading it.
Java’s standard collections (like ArrayList, HashSet, and HashMap) use “fail-fast” iterators. Internally, they keep a counter called modCount. Every time you add or remove something, modCount goes up.
When you start a loop, the iterator grabs the current modCount. Every time you call next(), it checks if the count has changed. If you modified the collection directly (not through the iterator), the counts won’t match, and Java throws the exception immediately rather than risking weird behavior later.
Common Trap: The Enhanced For-Loop
The “for-each” loop is where most people trip up. It looks clean, but under the hood, Java is creating an iterator for you.
for (User user : users) {
if (user.isInactive()) {
users.remove(user); // The iterator doesn't know you did this!
}
}
When the loop tries to go to the next element, the hidden iterator sees that the list’s modCount changed, but its own expected count didn’t. Crash.
How to Fix It Properly
1. The Iterator Approach
If you’re on an older version of Java or need complex logic, use the iterator’s own remove() method. This tells the iterator “I’m removing this, so update your internal count too.”
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if (s.isEmpty()) {
it.remove(); // This is the safe way
}
}
2. Collection.removeIf()
Since Java 8, this is almost always the best way to handle removals. It’s clean, readable, and handles the iterator logic for you.
list.removeIf(s -> s.isEmpty());
3. Loop Backwards
If you’re using a simple index-based for loop (not a for-each), you can avoid the exception by looping backwards. This way, when you remove an item, the indices of the items you haven’t visited yet don’t shift.
for (int i = list.size() - 1; i >= 0; i--) {
if (shouldRemove(list.get(i))) {
list.remove(i);
}
}
4. Create a Temporary List
If you’re doing something more complex than just removing, sometimes it’s easier to just collect the items you want to remove and then call removeAll() at the end.
List<String> toRemove = new ArrayList<>();
for (String s : list) {
if (isBad(s)) toRemove.add(s);
}
list.removeAll(toRemove);
What About Actual Multi-Threading?
If you actually have multiple threads touching the same collection, you’ve got a different problem. Standard collections aren’t thread-safe.
For those cases, look into:
CopyOnWriteArrayList: Great for lists that are read often but rarely changed.ConcurrentHashMap: The go-to for thread-safe maps.Collections.synchronizedList(): A quick wrapper, though often slower than the concurrent classes.
Conclusion
ConcurrentModificationException is basically Java’s “safety first” policy in action. It prevents you from ending up with corrupted data or impossible-to-debug “off by one” errors that would happen if the collection shifted while you were mid-loop.
Next time it happens, don’t fight the loop—just switch to removeIf or use an explicit Iterator.
Having trouble with a weird stack trace? Try Debugly’s formatter to get to the bottom of it faster.