Java Academy · Lesson

Fail-Fast vs Fail-Safe Iterators

Understand ConcurrentModificationException, fail-fast behavior, and when to use snapshot iterators.

Lesson 4 of 414 steps

Fail-Fast vs Fail-Safe Iterators is a free Java Academy lesson on CoddyKit. This is 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, and your progress syncs across the web and the CoddyKit app. The Java Academy course includes 4 lessons in total.

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 results

Safe 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 copy

ConcurrentHashMap 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 consistent

Collections.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 thread

Snapshot 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
Free to start

Learn Java with an AI tutor — free

Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.

Courses
104
Lessons
374

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. 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. The Java Academy course includes 4 lessons in total.

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, so you can start here or from the beginning and move at your own pace. This is lesson 4 of 4.

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

  1. The Iterable and Iterator Contracts
  2. Implementing a Custom Iterator
  3. ListIterator and Bidirectional Traversal
  4. Fail-Fast vs Fail-Safe Iterators
← Back to Java Academy