Understanding Fail-Fast and Fail-Safe Iterators in Java: Explained with Code Examples

Iterators in Java are used to traverse collections of elements. Two important concepts related to iterators are “Fail-Fast” and “Fail-Safe.” These terms describe the behavior of iterators when the underlying collection is modified during iteration. In this post, we will delve into these concepts, explain their differences, and provide code examples to illustrate their behavior.

Fail-Fast Iterators

A Fail-Fast iterator immediately throws a ConcurrentModificationException if the collection is structurally modified after the iterator is created. This is a safety mechanism to prevent concurrent modification during iteration.

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class FailFastExample {

    public static void main(String[] args) {
        List list = new ArrayList<>();
        list.add("A");
        list.add("B");
        list.add("C");

        Iterator iterator = list.iterator();

        while (iterator.hasNext()) {
            String element = iterator.next();
            System.out.println(element);
            list.add("D");  // Concurrent modification
        }
    }
}

In this example, attempting to modify the list by adding an element during iteration will result in a ConcurrentModificationException being thrown.

Fail-Safe Iterators

A Fail-Safe iterator doesn’t throw an exception if the collection is modified during iteration. Instead, it operates on the original copy of the collection before the modification.

import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;

public class FailSafeExample {

    public static void main(String[] args) {
        ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
        map.put("A", "Apple");
        map.put("B", "Banana");
        map.put("C", "Cherry");

        Iterator iterator = map.keySet().iterator();

        while (iterator.hasNext()) {
            String key = iterator.next();
            System.out.println(key + " - " + map.get(key));
            map.put("D", "Date");  // Safe modification
        }
    }
}

In this example, we use a ConcurrentHashMap, and modifying the map while iterating does not throw any exception. The iterator continues with the original set of keys.

Key Differences

  1. Behavior on Modification:
    • Fail-Fast: Throws ConcurrentModificationException immediately on structural modification.
    • Fail-Safe: Continues iterating on the original copy, ignoring modifications.
  2. Use Cases:
    • Fail-Fast: Useful when you want to detect and prevent concurrent modification for the sake of data consistency.
    • Fail-Safe: Useful when you don’t want iteration to be affected by modifications, and you’re okay with operating on the original collection snapshot.
  3. Collections:
    • Fail-Fast: Iterators from non-concurrent collections (e.g., ArrayList, HashMap) are usually fail-fast.
    • Fail-Safe: Concurrent collections (e.g., ConcurrentHashMap, CopyOnWriteArrayList) provide fail-safe iterators.

Conclusion

Understanding the differences between Fail-Fast and Fail-Safe iterators is essential for handling collections effectively in a multi-threaded environment. Depending on your use case, you can choose the appropriate iterator type to ensure your application behaves as expected when modifications to the collection occur during iteration. Always consider the specific requirements of your application and the nature of your collections to make an informed decision.

You may also like...