Iterator and ListIterator in Java
Remove "B" from the list [A, B, C] inside a for-each loop and the program finishes without a single error — the loop simply never reaches "C". Remove "A" instead and the very next step throws ConcurrentModificationException. Both results are explained by the iterator that the for-each loop hides from you.
Iterator (java.util.Iterator<E>) is the Java interface for walking through a collection one element at a time: hasNext() checks whether another element exists, next() returns it, and remove() safely deletes the last returned element during the iteration. ListIterator extends Iterator for lists: it moves in both directions and can replace and insert elements. Both interfaces appeared in Java 1.2 together with the Collections Framework.
What is an Iterator in Java
An Iterator in Java is a cursor object that visits the elements of a collection one by one without exposing how the collection stores them, so the same loop works for an ArrayList, a HashSet or an ArrayDeque. You get one by calling iterator(), a method declared in the Iterable interface and therefore available on every Collection.
Iteration simply means visiting the elements of a group one after another. Iterator lives in the java.util package, so your code needs import java.util.Iterator;. The interface is generic: for a List<String>, iterator() returns an Iterator<String>, and the result of next() needs no cast. Iterator is an interface, not a class: each collection returns its own private implementation, for example java.util.ArrayList$Itr (you will see that name in the first example). The full contract is in the Iterator Javadoc.
The cursor of an iterator never sits on an element; it sits between elements. A fresh iterator is positioned before the first element, and each call to next() steps over one element and returns it. Once the cursor is past the last element, hasNext() returns false. A plain Iterator cannot move back or be “rewound” — for another pass you ask the collection for a new iterator.
Before Java 1.2 the same job was done by the Enumeration interface with hasMoreElements() and nextElement(), but it could not remove elements. Today you meet it only in legacy classes such as Vector and Hashtable.
Java Iterator methods
The Iterator interface has four methods: hasNext() and next() for moving forward, remove() for deleting the current element, and forEachRemaining() for processing all remaining elements with one lambda. Only hasNext() and next() are abstract; the other two have been default methods since Java 8.
| Method | What it does | Edge case |
|---|---|---|
boolean hasNext() | Returns true if there is another element after the cursor | Does not move the cursor: you can call it any number of times in a row |
E next() | Returns the next element and moves the cursor past it | No more elements — NoSuchElementException |
default void remove() | Removes from the collection the element returned by the last next() | Called before next() or twice in a row — IllegalStateException; on an unmodifiable collection and in the default implementation — UnsupportedOperationException |
default void forEachRemaining(Consumer<? super E> action) | Runs the action for each remaining element (Java 8) | Elements already returned by next() are not processed |
Iterating a collection with hasNext() and next()
The standard Java iterator loop is while (iterator.hasNext()) with exactly one call to iterator.next() inside. hasNext() only checks whether a next element exists; next() returns it and moves the cursor forward.
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class IteratorExample {
public static void main(String[] args) {
List<String> arrayList = new ArrayList<>();
arrayList.add("C");
arrayList.add("A");
arrayList.add("E");
arrayList.add("B");
arrayList.add("D");
arrayList.add("F");
Iterator<String> iterator = arrayList.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
System.out.print(element + " ");
}
System.out.println();
System.out.println(iterator.getClass().getName());
}
} Output:
C A E B D F
java.util.ArrayList$Itr The iterator type matches the element type of the list, Iterator<String>, so next() returns a String directly. The second line shows the actual class behind the interface: a private nested class of ArrayList. The iteration order is defined by the collection: index order for an ArrayList, no guaranteed order for a HashSet, heap order for a PriorityQueue (see why the PriorityQueue iterator does not sort).
Common mistake: calling next() twice per iteration
Every call to next() takes a new element, even when two calls stand in the same expression. What does this code print?
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
public class DoubleNextExample {
public static void main(String[] args) {
List<String> letters = new ArrayList<>(Arrays.asList("C", "A", "E", "B", "D"));
Iterator<String> it = letters.iterator();
while (it.hasNext()) {
System.out.println(it.next() + " " + it.next());
}
}
} Output:
C A
E B
Exception in thread "main" java.util.NoSuchElementException hasNext() guaranteed only one more element, "D", and the second next() on the same line ran past the end of the list. If you need the element several times in the loop body, store it in a variable: String s = it.next();.
Removing elements with Iterator.remove()
The only safe way to remove elements from a collection while iterating over it is the iterator’s own remove() method: it deletes the element returned by the last next() and keeps the iterator in sync with the collection. Calling list.remove() in the same loop breaks the iterator and leads to ConcurrentModificationException.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
public class IteratorRemoveExample {
public static void main(String[] args) {
List<String> words = new ArrayList<>(Arrays.asList("java", "", "list", "", "set"));
Iterator<String> it = words.iterator();
while (it.hasNext()) {
if (it.next().isEmpty()) {
it.remove(); // removes the element returned by next()
}
}
System.out.println(words);
Iterator<String> it2 = words.iterator();
it2.remove(); // next() has not been called yet
}
} Output:
[java, list, set]
Exception in thread "main" java.lang.IllegalStateException You may call remove() at most once after each next(): a second call in a row also throws IllegalStateException. On unmodifiable collections, such as Collections.unmodifiableList(...) or List.of(...) in Java 9+, the iterator’s remove() throws UnsupportedOperationException.
Since Java 8 there is a shorter way to remove by a condition: words.removeIf(String::isEmpty). It walks the collection internally, so you write no iterator loop; the method is covered in removeIf(): remove elements by a condition. An explicit iterator is still the tool of choice when removal comes with extra work: counting, logging, or moving the element into another collection.
The forEachRemaining() method
The forEachRemaining(Consumer) method runs the given action for every element the iterator has not returned yet, replacing the while loop with one line. The key word is remaining: elements already taken with next() are not processed again.
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
public class ForEachRemainingExample {
public static void main(String[] args) {
List<String> list = Arrays.asList("A", "B", "C", "D");
Iterator<String> iterator = list.iterator();
System.out.println("First: " + iterator.next());
iterator.forEachRemaining(str -> System.out.print(str + "+"));
System.out.println();
System.out.println(iterator.hasNext());
}
} Output:
First: A
B+C+D+
false The action is a Consumer — a lambda or a method reference. A typical use case is to handle a header line separately and then all the other lines. After forEachRemaining() the iterator is exhausted: hasNext() returns false. If you just need to process the whole collection, calling forEach() on the collection itself is shorter — see Iterating a collection: iterator, forEach and stream.
ConcurrentModificationException: why it happens
ConcurrentModificationException is thrown by an iterator’s next() when the collection was structurally modified after the iterator was created by anything other than that iterator: add(), remove() or clear() on the collection itself. It happens in single-threaded code too — “concurrent” here means “at the same time as the iteration”, not “from another thread”.
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class ConcurrentModificationExample {
public static void main(String[] args) {
List<String> arrayList = new ArrayList<>();
arrayList.add("C");
arrayList.add("A");
arrayList.add("E");
Iterator<String> iterator = arrayList.iterator();
arrayList.remove(0); // modification that bypasses the iterator
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
} Output:
Exception in thread "main" java.util.ConcurrentModificationException How it works: ArrayList, HashMap and the other java.util collections keep a counter of structural modifications, modCount. An iterator copies its value on creation (expectedModCount) and compares the two in every next(). If they differ, the iterator can no longer tell where its cursor points, so it fails immediately. Iterators that behave like this are called fail-fast. Iterator.remove() updates both counters, which is why removing through it is safe.
What does this code print? Removing inside for-each
A for-each loop uses the same kind of iterator, so the same rule applies to it. The result, however, depends on which element you remove:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ForEachRemoveExample {
public static void main(String[] args) {
List<String> letters = new ArrayList<>(Arrays.asList("A", "B", "C"));
for (String s : letters) {
if (s.equals("B")) {
letters.remove(s);
}
}
System.out.println(letters);
List<String> letters2 = new ArrayList<>(Arrays.asList("A", "B", "C"));
for (String s : letters2) {
if (s.equals("A")) {
letters2.remove(s);
}
}
}
} Output:
[A, C]
Exception in thread "main" java.util.ConcurrentModificationException After "B" is removed, the cursor is at position 2 and the list size has also become 2. hasNext() of ArrayList compares exactly these two numbers, returns false, and the loop ends before next() gets a chance to check modCount. "C" is silently skipped. After "A" is removed, the cursor is at 1 and the size is 2 — the loop continues, and next() detects the modification.
Fail-fast is best effort, not a guarantee
The Java documentation states that fail-fast behaviour works on a best-effort basis, so program correctness must never depend on ConcurrentModificationException. No exception does not mean correct code: the "B" example silently skipped an element. Modify a collection during iteration only through Iterator.remove(), ListIterator.add()/set() or with removeIf().
Ways to change a collection during iteration without the exception:
| Task | How | Note |
|---|---|---|
| Remove elements while iterating | iterator.remove() | Works for any modifiable collection |
| Remove by a condition | collection.removeIf(predicate) | Java 8; one line instead of a loop |
| Replace or insert a list element | listIterator.set() / add() | Lists only — see the ListIterator example |
| Add elements that should not be visited | Iterate over a copy: for (String s : new ArrayList<>(list)) | The copy costs O(n) memory |
| Other threads modify the collection | CopyOnWriteArrayList, ConcurrentHashMap | Their iterators never throw this exception — see Thread-safe collections in Java |
The same rule applies when you iterate over a Map through keySet(), values() or entrySet(): calling map.remove(key) inside the loop causes the same exception, while iterator.remove() on the entrySet() iterator removes the key–value pair safely.
The for-each loop and the Iterable interface
The for-each loop (for (String s : list)) works with arrays and with any object that implements the Iterable interface. For an Iterable, the compiler turns the loop into an ordinary iterator loop — which is why for-each follows every rule from the ConcurrentModificationException section.
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
public class ForEachDesugarExample {
public static void main(String[] args) {
List<String> arrayList = Arrays.asList("C", "A", "E");
// What you write
for (String element : arrayList) {
System.out.print(element + " ");
}
// What the compiler generates
for (Iterator<String> i = arrayList.iterator(); i.hasNext(); ) {
String element = i.next();
System.out.print(element + " ");
}
}
} Output:
C A E C A E The iterator variable of a for-each loop is hidden, so you cannot call remove() on it. If you need to remove elements while iterating, write the iterator explicitly; if you need the index, use an index-based for loop (see Iterating a List in Java). For arrays, for-each compiles into an index loop and no iterator is created.
The Iterable interface (java.lang.Iterable<T>, Java 5) describes an object you can iterate over: it has one abstract method, Iterator<T> iterator(), and two Java 8 default methods, forEach(Consumer) and spliterator(). It lives in java.lang, so it needs no import. Collection extends Iterable, which is why for-each works with every list, set and queue.
Map, however, does not implement Iterable, and the following class deliberately does not compile:
import java.util.HashMap;
import java.util.Map;
public class MapForEachError {
public static void main(String[] args) {
Map<String, Integer> ages = new HashMap<>();
ages.put("Anna", 30);
for (String key : ages) { } // compile error
}
} javac reports (source line and caret omitted):
error: for-each not applicable to expression type
required: array or java.lang.Iterable
found: Map<String,Integer> You iterate not over the map itself but over one of its views: ages.keySet(), ages.values() or ages.entrySet(). Iterating a map through entrySet() is covered in the Map lesson of this module.
How to write a custom Iterator and Iterable
To make objects of your own class usable in a for-each loop, the class implements Iterable<T>, and its iterator() method returns a new Iterator<T> object with hasNext() and next() on every call. Implementing remove() is optional.
Here is a range of integers that stores no elements at all and computes them on the fly:
import java.util.Iterator;
import java.util.NoSuchElementException;
public class Range implements Iterable<Integer> {
private final int from;
private final int to; // exclusive
public Range(int from, int to) {
this.from = from;
this.to = to;
}
@Override
public Iterator<Integer> iterator() {
return new Iterator<Integer>() {
private int current = from;
@Override
public boolean hasNext() {
return current < to;
}
@Override
public Integer next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return current++;
}
};
}
public static void main(String[] args) {
Range range = new Range(1, 4);
for (int n : range) {
System.out.print(n + " ");
}
System.out.println();
range.forEach(n -> System.out.print(n * 10 + " "));
System.out.println();
range.iterator().remove();
}
} Output:
1 2 3
10 20 30
Exception in thread "main" java.lang.UnsupportedOperationException: remove What matters in this implementation:
iterator()returns a new iterator on every call, sorangecan be iterated any number of times. If it returned the same iterator stored in a field, a second for-each loop would print nothing.- By contract,
next()throwsNoSuchElementExceptionwhen the elements run out; it does not returnnullor the last value. - The
forEach()method comes for free — it is a default method ofIterable. remove()is not overridden, so the default version fromIteratorruns and throwsUnsupportedOperationExceptionwith the messageremove.- The anonymous class is written as
new Iterator<Integer>() { ... }, so it compiles on Java 8. The diamond formnew Iterator<>() { ... }is allowed only since Java 9.
ListIterator interface in Java
ListIterator<E> is the list iterator in Java: it extends Iterator, walks a list in both directions, reports the index of its position, and can not only remove but also replace (set()) and insert (add()) elements during the iteration. You get it with listIterator() from any List — ArrayList, LinkedList and others.
Set and Queue have no such iterator: positions and indexes exist only in lists. listIterator() comes in two versions:
listIterator()— the cursor is before the first element (position 0);listIterator(int index)— the cursor is before the element atindex.list.listIterator(list.size())puts the cursor at the end, which is how you start iterating from the back. An index below 0 or abovesize()throwsIndexOutOfBoundsException.
For a three-element list the cursor can be in one of four positions:
A B C
^ ^ ^ ^
0 1 2 3 <- cursor position = nextIndex() next() returns the element to the right of the cursor, previous() the element to the left. So alternating next() and previous() returns the same element again and again. To iterate in reverse without an iterator, Java 21 offers list.reversed() (see Collection hierarchy in Java), while TreeSet and Deque provide descendingIterator() (see the TreeSet iterator and Deque interface in Java).
ListIterator methods
ListIterator adds six methods of its own to the three of Iterator: hasPrevious() and previous() for moving backwards, nextIndex() and previousIndex() for the cursor position, set() for replacing and add() for inserting an element.
| Method | What it does | Edge case |
|---|---|---|
boolean hasNext() | Whether there is an element to the right of the cursor | At the end of the list — false |
E next() | Returns the element to the right and moves the cursor forward | No element — NoSuchElementException |
boolean hasPrevious() | Whether there is an element to the left of the cursor | At the start of the list — false |
E previous() | Returns the element to the left and moves the cursor back | No element — NoSuchElementException |
int nextIndex() | Index of the element the next next() would return | At the end of the list returns size() |
int previousIndex() | Index of the element the next previous() would return | At the start of the list returns -1 |
void remove() | Removes the element returned by the last next() or previous() | Before next()/previous() or right after add() — IllegalStateException |
void set(E e) | Replaces the element returned by the last next() or previous() | After add() or remove() — IllegalStateException; works on Arrays.asList() |
void add(E e) | Inserts an element at the cursor, before the element next() would return | The next next() does not return the inserted element; on Arrays.asList() and List.of() — UnsupportedOperationException |
ListIterator example: set(), add() and backward iteration
The typical job for a ListIterator is to change list elements in place during the iteration and then walk the list in reverse order. In the first example set() appends a “+” to every element, and then the same iterator, now standing at the end, goes back with hasPrevious() and previous().
import java.util.Arrays;
import java.util.List;
import java.util.ListIterator;
public class ListIteratorExample {
public static void main(String[] args) {
List<String> arrayList = Arrays.asList("A", "B", "C", "D");
ListIterator<String> listIterator = arrayList.listIterator();
while (listIterator.hasNext()) {
String element = listIterator.next();
listIterator.set(element + "+");
}
System.out.print("Modified list: ");
System.out.println(arrayList);
System.out.print("Modified list in reverse order: ");
while (listIterator.hasPrevious()) {
String element = listIterator.previous();
System.out.print(element + " ");
}
}
} Output:
Modified list: [A+, B+, C+, D+]
Modified list in reverse order: D+ C+ B+ A+ The list comes from Arrays.asList(), which is a fixed-size list: set() works, but add() and remove() throw UnsupportedOperationException (see Immutable collections). The second while loop does not create a new iterator: after the first loop the cursor is already at the end of the list.
In the second example the list is modifiable. In one step the iterator first replaces "B" and then inserts an element after it, removes "C", and a new iterator created with listIterator(list.size()) walks the list from the end, printing indexes. The last lines show what happens when the order of set() and add() is reversed:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ListIterator;
public class ListIteratorEditExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
ListIterator<String> it = list.listIterator();
while (it.hasNext()) {
String s = it.next();
if (s.equals("B")) {
it.set("b"); // replace "B" first...
it.add("B2"); // ...then insert after it
}
if (s.equals("C")) {
it.remove();
}
}
System.out.println(list);
ListIterator<String> back = list.listIterator(list.size());
while (back.hasPrevious()) {
System.out.print(back.previousIndex() + ":" + back.previous() + " ");
}
System.out.println();
ListIterator<String> wrong = list.listIterator();
wrong.next();
wrong.add("X");
wrong.set("Y"); // set() right after add()
}
} Output:
[A, b, B2]
2:B2 1:b 0:A
Exception in thread "main" java.lang.IllegalStateException Note that "B2" was placed before the cursor, so the following next() returned "C", not the element just inserted — the loop never runs forever, even if you insert on every step. There is no ConcurrentModificationException: every change went through the iterator’s methods. On a LinkedList the same add() and remove() calls take O(1), which is the main reason to use a ListIterator there (see Iterating a LinkedList and inserting in the middle).
set() after add() throws
set() and remove() act on the element returned by the last next() or previous(). After add() or remove() there is no such “current” element any more, so the next set() throws IllegalStateException, as the last line of the example shows. If one step must both replace an element and insert a new one, call set() first and add() second.
Iterator vs ListIterator vs for-each: which to use
If you only read a collection, use the for-each loop: it is shorter and leaves no room for a stray next(). Use an explicit Iterator when you remove elements during the iteration, and a ListIterator when a list needs elements replaced or inserted, when you need the index, or when you walk from the end.
| Capability | for-each | Iterator | ListIterator |
|---|---|---|---|
| Works with | Any Iterable and arrays | Any Iterable | Only List |
| Direction | Forward | Forward | Forward and backward |
| Remove while iterating | No — ConcurrentModificationException | remove() | remove() |
| Replace an element | No | No | set() |
| Insert an element | No | No | add() |
| Index of the current position | No | No | nextIndex(), previousIndex() |
| When to choose | Read-only access — the default | Removal with extra logic | Editing a list in place, iterating from the end |
For simple cases the collections offer ready-made methods that hide the iterator: forEach() to run an action on every element, removeIf() to remove by a condition, and List.replaceAll() (Java 8) to replace every element of a list — for example, list.replaceAll(s -> s + "+") instead of the first set() example.
Frequently Asked Questions
What is the difference between Iterator and Iterable in Java?
Iterable is what you iterate over (a collection, a custom range class): it has the iterator() method. Iterator is how you iterate: a single-use cursor with state and the hasNext() and next() methods. One Iterable can hand out any number of independent iterators. More in The for-each loop and the Iterable interface and How to write a custom Iterator.
How do I get an iterator for an array in Java?
Arrays have no iterator() method. For an object array use Arrays.asList(array).iterator() or Arrays.stream(array).iterator(); for an int[], Arrays.stream(numbers).iterator() returns a PrimitiveIterator.OfInt whose nextInt() avoids autoboxing. For plain reading you need no iterator at all: for-each works with arrays directly.
Can I iterate a collection twice with the same iterator?
No: Iterator has no reset method, and after the last element hasNext() keeps returning false. For another pass call iterator() again. A ListIterator is the exception — it can walk back to the start with previous() — but creating a new one is simpler.
What is the difference between fail-fast and fail-safe iterators?
Fail-fast iterators of ordinary collections (ArrayList, HashMap) throw ConcurrentModificationException if the collection is modified behind the iterator’s back. Iterators of the java.util.concurrent collections never throw it: CopyOnWriteArrayList iterates over a snapshot taken when the iterator was created, while ConcurrentHashMap iterates over live data where changes may or may not be visible (the documentation calls this weakly consistent). The term “fail-safe” does not appear in the Java documentation, but it is a common interview question.
Comments