0Pricing
Java Academy · Lesson

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

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

  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