Iterable describes a Java object that can supply an iterator. Iterator represents one traversal with a current position. The two roles are connected but different: a collection can be iterable while several separate iterators independently move through its elements.
Review the related foundation lesson when you need a prerequisite. The complete programs below use Java 17-compatible syntax.
The traversal protocol
Iterable describes a source that can provide an Iterator. Enhanced for uses this protocol for ordinary collections.
Iterable has an iterator method that returns an Iterator. An enhanced for loop over an iterable asks for an iterator and uses it to advance through the values. Obtaining a second iterator normally starts a separate traversal for an ordinary collection. The iterator is not the collection itself, so consuming one traversal does not automatically empty or permanently consume the underlying list.
Iterator position and exhaustion
Iterator tracks a traversal position through hasNext and next. Calling next after exhaustion throws NoSuchElementException.
hasNext reports whether another element is available, and next returns that element while advancing. next without an available element throws NoSuchElementException. Calling hasNext repeatedly does not normally advance an ordinary collection iterator. Keep the condition and advancement separate so a loop neither skips entries nor reads past the end.
Supported iterator removal
An iterator may support remove after a successful next call. Collection behavior differs, and unmodifiable iterators reject removal.
Iterator.remove is an optional operation that removes the last element returned by next. It cannot be called before next or twice for the same returned element without another next call. Removing directly from an ordinary mutable collection during iterator traversal can cause ConcurrentModificationException. Use the iterator's supported removal or a suitable higher-level operation such as removeIf for filtering.
Structural modification
Modifying a collection directly during iteration can trigger fail-fast behavior. This is a bug-detection aid, not a guarantee of thread safety or a substitute for synchronization.
Fail-fast behavior detects many structural modifications, but it is best-effort rather than a thread-safety guarantee. It is not a synchronization mechanism and must not be relied on to make concurrent access safe. Concurrent collections may provide different traversal contracts. A custom Iterable should document its iteration order, mutation behavior and whether each iterator represents fresh or shared traversal state.
Running the examples
Save name.java, compile it with javac --release 17 name.java, and run java name. Use each program's own public class name and filename.
How LabelIteratorRemoval produces its output
The ArrayList contains Oak, an empty string and Pine. Each next advances the iterator and returns one label. isBlank identifies the empty string, and iterator.remove deletes that last-returned element. Oak and Pine remain in their original sequence. No removal is requested directly on labels while the iterator is active, so traversal follows its supported mutation contract.
How IndependentIterators produces its output
first advances twice and returns Ada then Bo. second has not advanced during those calls, so its first next still returns Ada. Both traverse the same immutable list, but their positions are separate. The collection contains the same two elements after the calls because reading through an iterator does not remove them.
How IteratorFiltering produces its output
Each next returns one integer. For two and four, the remainder test identifies even values and cursor.remove deletes the element most recently returned. Three remains because its condition is false. Removal is performed through the traversal that returned the value, so the loop can continue without independently altering the list structure.
Know the iterator's end state
An empty collection returns an iterator whose hasNext is false immediately. A loop guarded by hasNext naturally handles that case without a special first-element read. After a nonempty traversal finishes, another next throws NoSuchElementException rather than restarting at the beginning. Obtain a new iterator for a new ordinary collection traversal. For practice, call hasNext twice before the first next and confirm that the first element is still returned once. Then consume every element and inspect hasNext again. These checks distinguish querying availability from advancing position and prevent a common loop mistake where next is accidentally called both in a condition and in the body.
Implementing Iterable for a custom value
A custom type can implement Iterable<String> by supplying a public iterator() method that returns an Iterator<String>. The returned iterator owns the traversal position; the custom object owns or exposes the underlying sequence. For example, a roster can delegate iterator() to an internal list's iterator, making enhanced for available without exposing indexed access. Each call should produce an appropriate traversal under the roster's documented rules. If clients must not remove roster members through iteration, return an iterator from an unmodifiable representation rather than accidentally exposing the internal list's supported remove operation. Also decide whether iteration follows current roster state or a snapshot. Iterable itself does not establish that policy, so mutation, ordering and thread-safety expectations still need a clear contract.
Java Summary
- Iterable describes a source that can provide an Iterator. Enhanced for uses this protocol for ordinary collections.
- Iterator tracks a traversal position through hasNext and next. Calling next after exhaustion throws NoSuchElementException.
- An iterator may support remove after a successful next call. Collection behavior differs, and unmodifiable iterators reject removal.
- Modifying a collection directly during iteration can trigger fail-fast behavior. This is a bug-detection aid, not a guarantee of thread safety or a substitute for synchronization.
- Continue with Java exercises, Java quizzes and the Java study roadmap.
Java Examples
Each example shows the code, the output it produces and what each part does.
Java Iterators and the Iterable Interface in a complete program
public class LabelIteratorRemoval {
public static void main(String[] args) {
java.util.List<String> labels = new java.util.ArrayList<>(java.util.List.of("Oak", "", "Pine"));
java.util.Iterator<String> iterator = labels.iterator();
while (iterator.hasNext()) {
if (iterator.next().isBlank()) iterator.remove();
}
System.out.println(labels);
}
}[Oak, Pine]Line-by-line explanation
- Line 8Evaluates
labelsand prints[Oak, Pine].
Two iterators have independent positions
public class IndependentIterators {
public static void main(String[] args) {
java.util.List<String> names = java.util.List.of("Ada", "Bo");
java.util.Iterator<String> first = names.iterator();
java.util.Iterator<String> second = names.iterator();
System.out.println(first.next());
System.out.println(first.next());
System.out.println(second.next());
}
}Ada
Bo
AdaLine-by-line explanation
- Line 6Evaluates
first.next()and printsAda. - Line 7Evaluates
first.next()and printsBo. - Line 8Evaluates
second.next()and printsAda.
Removal follows the returned element
public class IteratorFiltering {
public static void main(String[] args) {
java.util.List<Integer> numbers = new java.util.ArrayList<>(java.util.List.of(2, 3, 4));
java.util.Iterator<Integer> cursor = numbers.iterator();
while (cursor.hasNext()) {
int number = cursor.next();
if (number % 2 == 0) cursor.remove();
}
System.out.println(numbers);
}
}[3]Line-by-line explanation
- Line 9Evaluates
numbersand prints[3].
Java FAQ
Does hasNext move the cursor?
For ordinary collection iterators, it only checks whether another element is available. next performs the advancement. A while loop can safely test hasNext before each next instead of using exceptions as the normal termination condition.
Is Iterator.remove always available?
No. It is optional, and immutable collections commonly reject it. Check the source's contract. Even when supported, call it only after next and at most once for that returned element before advancing again.
What is a ListIterator?
A ListIterator, obtained from list.listIterator(), can move both forwards and backwards with next and previous, report the current index, and replace or insert elements with set and add during the traversal. It's only available for lists.
Can I reuse an iterator after it reaches the end?
No. An iterator moves in one direction and can't be reset. Ask the collection for a new one with iterator(), which starts again from the beginning. Each enhanced for loop automatically gets a fresh iterator.
Is removeIf better than Iterator.remove?
For removing every element that matches a condition, usually yes: list.removeIf(s -> s.isBlank()) says the intent in one line and avoids index and iterator mistakes. Use an explicit iterator when the decision depends on more complex logic or you also need to do other work during the same traversal.
Java Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Which type supplies an iterator?
Which operation advances an ordinary iterator?
Java Quiz
2 questions to check what you learned.
When can supported remove be called?
- After next, once for that returned element
- Before the first next
- Repeatedly without advancing
Does fail-fast iteration guarantee thread safety?
- No
- Yes for every ArrayList
- Only when hasNext is called twice