You are currently viewing Using Iterators and the for-each Loop Safely

Using Iterators and the for-each Loop Safely

Introduction

Iteration over collections is a fundamental operation in Java programming. Although it may appear simple at first glance, improper use of iteration constructs often leads to subtle and disruptive runtime issues. Among these, the ConcurrentModificationException is one of the most common errors faced by developers of all experience levels. Mastering safe iteration practices is therefore essential for building robust, predictable, and maintainable Java applications.
This article examines how to use Iterators and the enhanced for-each loop safely. It also explains when each construct is appropriate, why certain patterns should be avoided, and how modern alternatives can improve code clarity.

1. The Importance of Controlled Iteration

Iteration provides sequential access to elements in a collection. While Java offers several ways to iterate, each technique carries specific rules and constraints. Ensuring safety requires understanding how iteration interacts with mutation. Transitioning carefully between different structures—lists, sets, and maps—helps prevent runtime failures.

2. What Is an Iterator?

2.1. Definition

An Iterator is an object that lets you traverse a collection one element at a time.
The Iterator interface provides three key methods:

  • hasNext() → checks if more elements exist
  • next() → returns the next element
  • remove() → removes the last element returned by next()

Example: Basic use of an Iterator

List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bruno");
names.add("Chloé");

Iterator<String> it = names.iterator();

while (it.hasNext()) {
String name = it.next();
    System.out.println(name);
}

2.2. Safe Use of the Iterator Interface

Using the iterator’s remove() method is the only safe way to delete elements during traversal. This approach ensures the iterator’s internal state remains synchronized with the collection.

The following example illustrates safe behavior:

// Using an iterator to remove elements safely
Iterator<String> it = names.iterator();
while (it.hasNext()) {
    String value = it.next();
    if (value.startsWith("A")) {
        it.remove(); // Safe: iterator controls the modification
    }
}

3. The Enhanced For-each Loop and Its Safety Boundaries

The for-each loop improves readability by abstracting away explicit iterator usage. It is ideal when traversal alone is required. However, because it implicitly relies on an iterator, modifying the underlying collection directly within a for-each loop leads almost inevitably to a ConcurrentModificationException.

As quoted by Joshua Bloch in “Effective Java”:

“When you need to remove elements as you traverse a collection, you must use an explicit iterator.”

Consider this unsafe pattern:

// Unsafe removal during for-each traversal
for (String element : names) {
    if (element.startsWith("A")) {
        names.remove(element); // Unsafe: structural modification during traversal, will throw ConcurrentModificationException 
    }
}

This example is intentionally incorrect to highlight the importance of respecting traversal boundaries.

4. Modern Alternatives: removeIf and Streams

Modern Java promotes functional-style operations that encapsulate iteration safely and securely. The removeIf() method, introduced in Java 8, allows concise and expressive removal without manually manipulating iterators.

Example:

// Safe and concise removal using removeIf
names.removeIf(name -> name.startsWith("A"));

Streams also provide controlled and expressive traversal, although they do not permit direct modification of the source collection. Instead, they encourage collecting the results into a new structure. Each technique increases clarity while reducing exposure to iteration errors.

5. Iterating Safely Over Maps

Iterating over maps requires managing key–value pairs. Just like lists, maps must be modified only through the iterator that controls traversal. Using the iterator of entrySet() ensures safe removal of entries.

// Safe map iteration and conditional removal
Iterator<Map.Entry<String, Integer>> iter = scores.entrySet().iterator();
while (iter.hasNext()) {
    Map.Entry<String, Integer> entry = iter.next();
    if (entry.getValue() < 10) {
        iter.remove(); // Safe structural modification through iterator
    }
}

Understanding these rules is especially important because maps underlie many performance-critical Java features. To learn more about working with Map structures, refer to our article on Understanding HashMap Internal Structure.

Conclusion

Safe iteration in Java requires more than simply choosing between an Iterator and a for-each loop. It demands awareness of how each construct interacts with mutation, structure, and underlying traversal mechanisms. By applying the principles described in this article—especially using the iterator’s remove() method and preferring modern alternatives such as removeIf()—developers can avoid painful runtime errors and write cleaner, more reliable code.

You can find the complete code for this article on GitHub.

Noel Kamphoa

Experienced software engineer with expertise in Telecom, Payroll, and Banking. Now Senior Software Engineer at Societe Generale Paris.