Java Iterator hasNext and next: How They Work
java iterator hasnext next: Learn how Java's Iterator hasNext() and next() methods work together for safe collection traversal, including common mistakes and custom it...
The Iterator Interface and Its Core Methods
Java's Iterator<E> interface defines two methods that form the backbone of collection traversal: hasNext() and next(). The contract is simple: hasNext() reports whether another element exists, and next() returns that element and advances the internal cursor. Every call to next() must be guarded by a hasNext() check when the end of the collection is not already known. This pairing is what makes the java iterator hasnext next pattern safe for loops that process collections of unknown size.
How hasNext() and next() Interact at Runtime
The two methods work as a pair with distinct responsibilities. hasNext() does not change the iterator state; it only inspects whether the cursor has reached the end of the underlying collection. next() is the only method that advances the cursor. This separation exists so that you can inspect the collection without consuming elements, which is essential for loops, filtering, and lookahead patterns where you need to peek at the next element before deciding whether to consume it.
The state lives inside the iterator implementation. For an ArrayList, the iterator tracks an index and a size. For a LinkedList, it tracks a node reference. The hasNext() method compares the current position against the known end, while next() fetches the element at the current position and increments the cursor. The exact mechanics differ by collection, but the public contract is identical.
A Minimal Iteration Example
List<String> names = Arrays.asList("Ada", "Grace", "Linus"); Iterator<String> iterator = names.iterator(); while (iterator.hasNext()) { String name = iterator.next(); System.out.println(name); }
This loop prints each name in order. The hasNext() call runs before every next() call, so the loop terminates cleanly when the cursor reaches the end. If the list is empty, hasNext() returns false on the first check and the loop body never executes.
The same pattern works for Set, Queue, and any other Collection implementation. The iterator abstracts away the underlying storage, so the traversal code does not need to know whether it is reading from an array, a linked structure, or a hash table.
Common Mistakes with hasNext() and next()
The most frequent error is calling next() without checking hasNext() when the end of the collection is unknown. Consider this code:
Iterator<String> iterator = names.iterator(); String first = iterator.next(); // safe only if the list is non-empty
If names is empty, this throws NoSuchElementException. The compiler cannot detect this because next() is a runtime call. The exception is a subclass of RuntimeException, so it is not checked at compile time.
Another common mistake is reusing an iterator after it has been exhausted. An iterator is a one-shot object. Once hasNext() returns false, the iterator cannot be reset. You must obtain a fresh iterator from the collection to traverse it again.
A subtler issue is assuming that hasNext() remains true after a single next() call. The state changes on every next() call, so a loop that calls next() twice per iteration must verify that two elements remain before the second call.
The Cost of Calling next() Without hasNext()
Calling next() on an exhausted iterator throws NoSuchElementException immediately. The exception is thrown lazily at the call site, which means the failure appears where the traversal logic is written, not where the collection was created. This makes the error harder to trace when the iterator is passed between methods.
The runtime cost of an unguarded next() call is negligible in the happy path. The iterator simply returns the current element and advances the cursor. The problem is not performance; it is correctness. An uncaught NoSuchElementException terminates the current thread, which in a server application can take down a request handler or a background job.
Iterator vs Enhanced for Loop
for (String name : names) { System.out.println(name); }
The enhanced for loop compiles to the same iterator-based traversal when the target is an Iterable. The difference is that the loop hides the iterator entirely, which removes the possibility of calling next() without hasNext(). The compiler generates the guard for you.
Use the explicit iterator when you need to remove elements during traversal, when you need to interleave traversal across multiple collections, or when you need to skip elements conditionally. Use the enhanced for loop for simple read-only traversal, because it is shorter and eliminates a whole class of iterator misuse.
Removing Elements Safely During Iteration
Iterator<String> iterator = names.iterator(); while (iterator.hasNext()) { String name = iterator.next(); if (name.startsWith("A")) { iterator.remove(); } }
The remove() method on the iterator is the only safe way to delete an element while traversing. Calling List.remove() inside a for-each loop throws ConcurrentModificationException because the collection's structural modification count changes without the iterator's knowledge. The iterator's remove() method updates both the collection and the iterator's internal state, so the traversal remains consistent.
The remove() method may only be called once per next() call, and only after a next() call has been made. Calling it before the first next() or twice in a row throws IllegalStateException.
When to Implement Your Own Iterator
Implementing Iterator<T> directly is useful for custom data structures or for generating sequences on demand. The key requirement is maintaining consistent state between hasNext() and next().
class RangeIterator implements Iterator<Integer> { private int current; private final int end; RangeIterator(int start, int end) { this.current = start; this.end = end; } @Override public boolean hasNext() { return current < end; } @Override public Integer next() { if (!hasNext()) { throw new NoSuchElementException(); } return current++; } }
This iterator produces integers from start up to, but not including, end. The hasNext() method compares the current position against the end, and next() returns the current value before incrementing it. The guard inside next() mirrors the contract of the standard library iterators: calling next() past the end throws NoSuchElementException.
When implementing your own iterator, keep the state minimal and make the hasNext() check cheap. If hasNext() performs expensive work, such as reading from a stream or computing a hash, the cost is paid on every loop iteration. In those cases, consider caching the result of the check so that next() does not repeat the work.