Fail-Fast vs Fail-Safe Iterators
Understand ConcurrentModificationException, fail-fast behavior, and when to use snapshot iterators.
Fail-Fast vs Fail-Safe Iterators is a free Java Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Java Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Fail-Fast vs Fail-Safe Iterators
Java iterators are categorized by their behavior when the underlying collection is modified during iteration: fail-fast iterators throw immediately; fail-safe iterators continue on a snapshot.
Fail-Fast Iterators
Most standard Java collections (ArrayList, HashMap, TreeSet) use fail-fast iterators. They track a modCount — a mutation counter. Any structural modification while iterating triggers ConcurrentModificationException.
import java.util.*;
List<String> list = new ArrayList<>(List.of("a", "b", "c"));
try {
for (String s : list) {
list.add("x"); // structural modification — throws!
}
} catch (ConcurrentModificationException e) {
System.out.println("ConcurrentModificationException caught!");
}
// Same for HashMap, TreeMap, HashSet, etc.Why Fail-Fast Exists
Fail-fast behavior is a debugging aid — it surfaces bugs immediately rather than allowing the iteration to continue with corrupted state. The alternative (silent corruption) is much harder to debug.
// modCount is incremented on every structural change
// (add, remove, clear on ArrayList)
// Iterator records modCount on creation
// On each next(), Iterator checks: if current modCount != expected, throw CME
// This detects bugs like:
List<Integer> nums = new ArrayList<>(List.of(1,2,3,4,5));
for (Integer n : nums) {
if (n == 3) nums.remove(n); // bug caught immediately
}
// ConcurrentModificationException — not silent wrong resultsSafe Removal: Iterator.remove()
The only safe modification during Iterator-based iteration is Iterator.remove() — it removes the last returned element and updates modCount.
List<Integer> nums = new ArrayList<>(List.of(1,2,3,4,5,6));
Iterator<Integer> it = nums.iterator();
while (it.hasNext()) {
int n = it.next();
if (n % 2 == 0) it.remove(); // safe: updates modCount
}
System.out.println(nums); // [1, 3, 5]removeIf: Modern Alternative
Java 8 added removeIf(Predicate) to Collection — a cleaner way to remove elements matching a condition without manual iterator management.
List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Ann", "Charlie"));
// Modern: removeIf handles iteration internally
names.removeIf(name -> name.startsWith("A"));
System.out.println(names); // [Bob, Charlie]
// Equivalent but verbose iterator approach:
Iterator<String> it = names.iterator();
while (it.hasNext()) {
if (it.next().startsWith("A")) it.remove();
}Fail-Safe Iterators: CopyOnWriteArrayList
CopyOnWriteArrayList uses a fail-safe iterator. It iterates over a snapshot taken at the time of iterator creation — modifications do not affect the ongoing iteration.
import java.util.concurrent.CopyOnWriteArrayList;
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.addAll(List.of("a", "b", "c"));
for (String s : list) {
list.add("x"); // no ConcurrentModificationException!
System.out.print(s + " "); // prints a, b, c (snapshot)
}
System.out.println();
System.out.println(list); // [a, b, c, x, x, x] — modified copyConcurrentHashMap Iterator
ConcurrentHashMap uses weakly consistent iterators — they reflect the state at some point during or after the iterator was created, no CME thrown, may or may not see concurrent updates.
import java.util.concurrent.ConcurrentHashMap;
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("a", 1); map.put("b", 2); map.put("c", 3);
for (Map.Entry<String, Integer> e : map.entrySet()) {
map.put("d", 4); // no CME — weakly consistent
System.out.println(e.getKey() + "=" + e.getValue());
}
// May or may not print "d=4" — weakly consistentCollections.synchronizedList
Collections.synchronizedList is NOT fail-safe — it still uses fail-fast iterators but blocks concurrent access. You must manually synchronize during iteration.
import java.util.*;
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
syncList.addAll(List.of("a", "b", "c"));
// Must synchronize during iteration!
synchronized (syncList) {
for (String s : syncList) {
System.out.println(s);
}
}
// Without the synchronized block, CME is still possible from another threadSnapshot Iterator Pattern
Create a snapshot manually to iterate safely over a mutable collection without synchronization.
import java.util.*;
List<String> original = new ArrayList<>(List.of("a", "b", "c"));
// Take a snapshot copy before iterating
List<String> snapshot = List.copyOf(original);
for (String s : snapshot) {
// Safe to modify original during snapshot iteration
original.remove(s);
System.out.println("Removed: " + s);
}
System.out.println(original); // []CopyOnWrite Tradeoffs
CopyOnWriteArrayList is fail-safe but expensive for writes. Each mutation creates a full copy.
- Good for: many reads, rare writes (event listener lists)
- Bad for: frequent mutations, large collections
Summary: Fail-Fast vs Fail-Safe
Comparison summary:
- Fail-fast: ArrayList, HashMap, TreeMap — throw CME on modification, modCount check
- Fail-safe/weakly consistent: ConcurrentHashMap, CopyOnWriteArrayList — no CME, iterate snapshot or concurrent data
- Safe modification methods: Iterator.remove(), removeIf(), replaceAll()
Practical: Expired Session Cleanup
Safely removing expired sessions from a concurrent list using appropriate patterns.
import java.util.*;
import java.util.concurrent.*;
class SessionManager {
private final CopyOnWriteArrayList<Session> sessions = new CopyOnWriteArrayList<>();
record Session(String id, long expiresAt) {
boolean isExpired() { return System.currentTimeMillis() > expiresAt; }
}
void add(Session s) { sessions.add(s); }
// Safe to call from multiple threads while iterating
void purgeExpired() {
sessions.removeIf(Session::isExpired);
}
List<Session> active() {
return sessions.stream().filter(s -> !s.isExpired()).toList();
}
}Quick Check
What exception does modifying an ArrayList while iterating throw?
Recap: Fail-Fast vs Fail-Safe Iterators
Key takeaways:
- Fail-fast iterators (ArrayList, HashMap) throw ConcurrentModificationException on concurrent modification
- Fail-safe iterators (CopyOnWriteArrayList, ConcurrentHashMap) allow modification without throwing
- Iterator.remove() is the only safe way to remove during fail-fast iteration
- removeIf() is the modern, cleaner alternative to manual Iterator.remove()
- CopyOnWriteArrayList: safe for read-heavy, write-rare scenarios
- ConcurrentHashMap: weakly consistent — no CME, may or may not see concurrent modifications
Frequently asked questions
Is the “Fail-Fast vs Fail-Safe Iterators” lesson free?
Yes — the full text of “Fail-Fast vs Fail-Safe Iterators” is free to read here on the web, and the Java Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Java Academy course, upgrade to CoddyKit PRO.
What will I learn in “Fail-Fast vs Fail-Safe Iterators”?
Understand ConcurrentModificationException, fail-fast behavior, and when to use snapshot iterators. You practise Java Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Java Academy?
No prior experience is required. Java Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Fail-Fast vs Fail-Safe Iterators” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Java Academy lesson?
Yes. Every Java Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- The Iterable and Iterator Contracts
- Implementing a Custom Iterator
- ListIterator and Bidirectional Traversal
- Fail-Fast vs Fail-Safe Iterators