🔴 The Error You're Seeing
Confirm this matches your console output. If it does, you're in the right place.
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1096)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:1050)
at CmeDemo.main(CmeDemo.java:11)⚡ Quick Fix Works 80% of the time
Remove through the iterator itself, or replace the whole loop with removeIf.
// Safe removal while iterating
for (Iterator<String> it = names.iterator(); it.hasNext(); ) {
if (it.next().equals("ravi")) {
it.remove();
}
}
// Or one line since Java 8:
names.removeIf(name -> name.equals("ravi"));🧠 Why this Happens
Tap to expand the deep technical explanation
ArrayList inherits a modCount revision counter from AbstractList; every structural change bumps it. When iterator() hands out an Itr, that object snapshots expectedModCount = modCount, and every next() first calls checkForComodification to compare the two values. list.remove() bumps only modCount, so the mismatch fires on the following advance; iterator.remove() bumps BOTH values, which is precisely why it can never trigger the exception. The check lives in next(), not hasNext(): after removing the second-to-last element the size drops, nextIndex() equals size, hasNext() returns false, the enhanced-for exits normally — and the poisoned comparison never executes. Despite the name, threads are entirely optional: any unsynchronized structural change during traversal triggers it.
The HITEC City Parking Spot Analogy:
The iterator is a tally clerk walking a conveyor belt with his own duplicate ledger. Yank an item off the belt behind his back (list.remove()) and his ledger stops matching reality — he halts the line at the next crate rather than certify wrong numbers. Hand items through him instead (it.remove()) and he updates his own ledger as he goes.
🔁 How to Reproduce Confirm this is your error
Create a four-element ArrayList ("raju", "ravi", "kiran", "suresh") and call names.remove(name) inside a for-each when name equals "ravi" — the very next iterator advance throws. Verified lab nuance: with only THREE elements, removing the second-to-last exits silently because hasNext() sees cursor == size after the shrink and never reaches checkForComodification. (Lab capture: OpenJDK 25.)
🛠️ Solutions (5 Ways to Fix)
Remove via it.remove(), never list.remove()
👉 Use this in single-threaded loops that must delete entries while scanning.
The iterator is the only writer allowed mid-traversal: its own remove() advances past the deleted element AND re-syncs its private expectedModCount with the list-wide modCount, so they stay equal. list.remove() bumps only modCount — the mismatch detonates at the next next(). It also decrements the internal cursor correctly, avoiding skipped-element bugs that index-based removal causes.
for (Iterator<String> it = names.iterator(); it.hasNext(); ) {
String name = it.next();
if (name.startsWith("test_")) {
it.remove();
}
}removeIf for condition-based bulk deletion
👉 Use this whenever survivors can be expressed as a predicate and no per-item logic is needed during removal.
Collection.removeIf arrived in Java 8 as a default method. ArrayList overrides it with a bitset-based implementation that shifts surviving elements once — faster than repeated single removes and structurally incapable of desynchronizing, because there is no user-facing iterator to go stale.
// Keep everything except disabled accounts
accounts.removeIf(a -> !a.isActive());
// Multi-condition
orders.removeIf(o -> o.isExpired() || o.value() == 0);Snapshot first, mutate after
👉 Use this when the walk must see a stable view or when removed and surviving items are needed separately afterwards.
Iterate over a copy so the original can change freely; every mutation happens while NO live iterator points at it. Costs one extra list — trivial against correctness — and streams give the same shape declaratively.
List<String> snapshot = new ArrayList<>(queue);
snapshot.forEach(item -> {
if (expired(item)) queue.remove(item); // safe: iterating the copy
});
// Declarative variant:
List<Order> stale = orders.stream().filter(Order::isStale).toList();
orders.removeAll(stale);Concurrent collections for genuinely parallel mutation
👉 Use this when OTHER threads modify the structure while yours iterates — every fix above assumes single-threaded access.
CopyOnWriteArrayList snapshots its backing array on each write; iterators walk that immutable picture and NEVER throw, trading an O(n) copy per write — ideal for read-mostly listener registries. ConcurrentHashMap iterators are weakly consistent: no exception ever, but they may reflect only part of concurrent updates. Choose by read/write ratio, not habit.
List<Listener> listeners = new CopyOnWriteArrayList<>();
for (Listener l : listeners) l.onEvent(evt); // safe under concurrent add/remove
Map<String, Stats> stats = new ConcurrentHashMap<>();
for (var e : stats.entrySet()) { /* weakly consistent view */ }Backwards index loop for positional compaction
👉 Use this on small random-access lists when positions are already tracked and zero extra allocations matter.
Walking indices from size()-1 down to 0 means every removal shifts only ALREADY-VISITED positions — nothing is skipped and no iterator exists to desynchronize. Avoid on LinkedList, where get(i) is O(n) per step and the combination turns quadratic immediately.
for (int i = users.size() - 1; i >= 0; i--) {
if (!users.get(i).hasConsent()) {
users.remove(i);
}
}📋 Version Notes
Collection.removeIf lands as a default method — the one-line bulk fix becomes universally available.
Fail-fast semantics unchanged; traces still show ArrayList$Itr.checkForComodification at the throw site (line numbers drift between builds).
SequencedCollection adds reverse()/reversed() views but does not relax fail-fast rules for their iterators.
🛡️ How to Prevent This Next Time
Treat enhanced-for bodies as read-only, funnel all removals through removeIf or explicit iterators, and reach for CopyOnWriteArrayList or ConcurrentHashMap only when threads genuinely share the structure.