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 existnext()→ returns the next elementremove()→ removes the last element returned bynext()
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.
