How to Iterate over a List in Java
Page content
In this tutorial, we’ll learn different ways to iterate over a list in Java.
Print List using for loop
Let’s print list of prime numbers using basic and enhanced for loop
List<Integer> primeNumbers = Arrays.asList(1, 2, 3, 5, 7);
// basic for loop
for(int i = 0; i < primeNumbers.size(); i++) {
System.out.println(primeNumbers.get(i));
}
// enhanced for loop
for (Integer number : primeNumbers) {
System.out.println(number);
}
Print List using forEach
Java 8 introduced forEach method to loop through a List which is very convenient and easy to use.
List<Integer> primeNumbers = Arrays.asList(1, 2, 3, 5, 7);
// Iterable.forEach with lambda expression
primeNumbers.forEach(number -> System.out.println(number));
// Iterable.forEach with method reference ::
primeNumbers.forEach(System.out::println);
// Stream.forEach with lambda expression
primeNumbers.stream().forEach(number -> System.out.println(number));
// Stream.forEach with method reference ::
primeNumbers.stream().forEach(System.out::println);
Print List using Iterator
Iterator is used to iterate over the list in forward direction.
- hasNext() Returns true if the iteration has more elements.
- next() Returns the next element in the iteration.
List<Integer> primeNumbers = Arrays.asList(1, 2, 3, 5, 7);
Iterator<Integer> iterator = primeNumbers.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
Print List using ListIterator
ListIterator is an iterator for lists that allows the programmer to traverse the list in either direction (forward or backward), and obtain the iterator’s current position in the list.
- hasNext() returns true if this list iterator has more elements when traversing the list in the forward direction.
- next() returns the next element in the list and advances the cursor position.
- hasPrevious() returns true if this list iterator has more elements when traversing the list in the reverse direction.
- previous() returns the previous element in the list and moves the cursor position backwards.
List<Integer> primeNumbers = Arrays.asList(1, 2, 3, 5, 7);
ListIterator<Integer> listIterator = primeNumbers.listIterator();
// iterate forward
while(listIterator.hasNext()) {
System.out.println(listIterator.next());
}
// iterate backward
while(listIterator.hasPrevious()) {
System.out.println(listIterator.previous());
}
Removing elements while iterating: the ConcurrentModificationException gotcha
It’s tempting to remove elements from a list right inside an enhanced for loop. It compiles cleanly and looks harmless:
1List<Integer> nums = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6));
2
3for (Integer n : nums) {
4 if (n % 2 == 0) {
5 nums.remove(n); // removes the Integer object equal to n
6 }
7}
Run it, and it throws:
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1095)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:1049)
...
An enhanced for loop over a List is syntactic sugar for an Iterator under the hood. ArrayList’s iterator tracks a modCount snapshot taken when it was created, and every next() call checks that snapshot against the list’s current modCount. Calling list.remove(...) directly on the list — bypassing the iterator — bumps modCount without the iterator knowing, so the very next next() call detects the mismatch and fails fast with ConcurrentModificationException. It’s fail-fast, not fail-safe: the point is to surface the bug immediately rather than let you silently skip elements (note in the example above that once the exception is caught, the list is left as [1, 3, 4, 5, 6] — the 4 was never even checked, because removing 2 shifted 4 into the index the iterator had already stepped past).
The fix is to remove through the iterator itself, using Iterator.remove(), which updates the iterator’s own bookkeeping so no mismatch is ever detected:
1List<Integer> nums = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6));
2
3Iterator<Integer> iterator = nums.iterator();
4while (iterator.hasNext()) {
5 Integer n = iterator.next();
6 if (n % 2 == 0) {
7 iterator.remove();
8 }
9}
10System.out.println(nums); // [1, 3, 5]
Since Java 8, if all you need is a removal-by-condition, Collection.removeIf() is shorter and does the same thing safely internally:
1nums.removeIf(n -> n % 2 == 0); // [1, 3, 5]
Modern alternatives: streams and Java 21’s SequencedCollection
Streams give you a functional way to iterate without an explicit loop at all — useful when you’re transforming or filtering rather than just printing:
1List<Integer> primeNumbers = List.of(1, 2, 3, 5, 7);
2
3List<Integer> doubled = primeNumbers.stream()
4 .map(n -> n * 2)
5 .toList();
Streams are not a drop-in replacement for the loops above when the body has side effects or needs to break early — forEach on a stream can’t break, and modifying the source collection mid-stream throws the same ConcurrentModificationException as an enhanced for loop.
Java 21 also added the SequencedCollection interface, which every List implements. It gives you getFirst()/getLast() and, more relevantly here, reversed() — a view of the list in reverse order that you can iterate with a plain enhanced for loop, no ListIterator required:
1List<Integer> primeNumbers = new ArrayList<>(List.of(1, 2, 3, 5, 7));
2
3for (int n : primeNumbers.reversed()) {
4 System.out.println(n); // 7, 5, 3, 2, 1
5}
For simple backward reading, .reversed() is now the more idiomatic choice on Java 21+. Reach for ListIterator specifically when you need to modify the list while walking it backward — set(), add(), or remove() mid-traversal — since reversed() gives you a view to read, not a cursor with mutation methods, and a plain Iterator only walks forward and only supports remove(), not set() or add().
Iterator vs ListIterator: which one do you need?
- Enhanced for loop — the default choice for simple read-only iteration; shortest to write, but no access to the index and no safe way to remove.
Iterator— forward-only, but adds the one thing the for loop can’t do safely:remove()mid-iteration. Reach for it whenever you’re conditionally removing elements from aList,Set, orMapwhile looping.ListIterator— everythingIteratoroffers, plus backward traversal, index access (nextIndex()/previousIndex()), and in-place mutation viaset()andadd(). Reach for it when you need to walk the list backward and modify it as you go — a plainIteratorcan’t move backward, and.reversed()can’t mutate.- Streams — best when iteration is really a transformation (
map,filter,collect) rather than a loop with side effects.