The ability to iterate over a collection is fundamental in any programming language. In Java, this is achieved using the Iterable and Iterator interfaces — core components of the Java Collections Framework.
But what if you want to define your own data structure and still support enhanced for-loops and streaming APIs? That's where creating a custom Iterable and Iterator becomes invaluable.
This guide will walk you through the purpose, implementation, and best practices of building your own iterable collections, whether for interview preparation, library development, or educational use.
What Are Iterable and Iterator in Java?
📌 Iterable<T>
- Introduced in Java 5.
- Interface with a single method:
Iterator<T> iterator(); - Enables enhanced for-loop (
for-each) support.
📌 Iterator<T>
- Interface with three primary methods:
boolean hasNext(); T next(); default void remove(); // Optional
Built-in Usage
List<String> names = List.of("Alice", "Bob");
for (String name : names) {
System.out.println(name);
}
Why Create a Custom Iterable?
- You have a custom data structure (e.g., a circular buffer or matrix).
- You want to control iteration behavior.
- You’re building a framework, API, or domain-specific collection.
- For better testability and encapsulation.
Step-by-Step Example: Creating a Custom Iterable
Let’s create a custom Range class that iterates over a sequence of integers.
Step 1: Create the Iterable Class
public class Range implements Iterable<Integer> {
private final int start;
private final int end;
public Range(int start, int end) {
this.start = start;
this.end = end;
}
@Override
public Iterator<Integer> iterator() {
return new RangeIterator();
}
private class RangeIterator implements Iterator<Integer> {
private int current = start;
@Override
public boolean hasNext() {
return current <= end;
}
@Override
public Integer next() {
return current++;
}
}
}
Step 2: Use the Iterable
public class TestRange {
public static void main(String[] args) {
for (int i : new Range(1, 5)) {
System.out.println(i);
}
}
}
Output:
1
2
3
4
5
Functional Support with Java 8+
Stream Support via Spliterator (Optional Advanced)
To make your custom Iterable streamable:
StreamSupport.stream(range.spliterator(), false)
.map(n -> n * 2)
.forEach(System.out::println);
You can override spliterator() for optimized performance.
Internal Mechanics
- The enhanced
forloop is syntactic sugar for callingiterator(), then looping usinghasNext()andnext(). Iteratoris stateful — it tracks where you are in the traversal.
Performance Considerations
| Factor | Impact |
|---|---|
| Memory efficiency | Depends on internal state logic |
| Thread safety | Custom iterator not thread-safe by default |
| Big-O complexity | Depends on backing structure |
Real-World Use Cases
- Database cursors – Custom iterator to fetch rows
- Pagination wrappers – Iterate over paginated APIs
- Custom DSLs or config loaders
- File readers that lazily load lines
Comparisons
| Feature | Iterable | Iterator |
|---|---|---|
| Purpose | Represents collection | Represents iteration |
| Method count | 1 (iterator()) |
2–3 (hasNext(), next(), remove()) |
| Supports for-each | ✅ Yes | ❌ No (not directly) |
| Functional friendly | ✅ (via Streams) | ✅ (manual conversion) |
Java Version Tracker
📌 What's New in Java?
- Java 8
- Default methods like
remove()inIterator StreamSupport.stream()forIterable
- Default methods like
- Java 9
- Enhanced support for
SpliteratorAPI
- Enhanced support for
- Java 10+
varfor inferring local variable types
- Java 21
- No direct changes to
Iterable/Iterator, but better parallel stream support
- No direct changes to
Best Practices
- Make iterators fail-fast if your structure is modifiable
- Avoid keeping mutable shared state in the iterator
- Consider implementing
remove()if mutation is meaningful - Document behavior clearly (e.g., inclusive/exclusive ranges)
- Return
Iterable<T>from APIs when possible — more flexible thanCollection<T>
Anti-Patterns
- Returning null from
next()— always throwNoSuchElementExceptionif exhausted - Modifying the collection structure during iteration without proper checks
- Overcomplicating iteration logic when simple looping suffices
Refactoring Legacy Code
- Convert indexed iteration to custom
Iterablefor better encapsulation - Wrap non-iterable structures like arrays or result sets in custom
Iterable - Replace anonymous iterator classes with lambda-based generators (Java 8+)
Conclusion and Key Takeaways
IterableandIteratorare cornerstones of collection traversal in Java.- Creating custom implementations improves flexibility, testability, and abstraction.
- Works seamlessly with enhanced for-loops and Java 8 Streams.
- Make sure your custom iterator is correct, efficient, and predictable.
FAQ – Custom Iterable and Iterator
-
Can I make my class Iterable and Serializable?
Yes, they’re unrelated interfaces. -
Do I need both Iterable and Iterator interfaces?
Yes —Iterableis the container,Iteratoris the traversal mechanism. -
Can I use enhanced for-loop on my custom class?
Yes, if it implementsIterable<T>. -
What happens if I call
next()with nohasNext()?
It should throwNoSuchElementException. -
Is my custom iterator reusable?
No. Iterators are typically one-time use. -
Can I stop iteration early?
Yes — break the loop or filter with Streams. -
Is it OK to return null in
next()?
Discouraged. Use exceptions for end-of-iteration. -
Are custom Iterators thread-safe?
Not by default — add synchronization if needed. -
Can I create an infinite iterator?
Yes, but be careful — always use with a limit condition. -
Should I use Iterable for APIs or Iterator?
Prefer returningIterable<T>— it's more flexible.