Creating Custom Iterable and Iterator in Java – Complete Guide with Examples

Illustration for Creating Custom Iterable and Iterator in Java – Complete Guide with Examples
By Last updated:

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 for loop is syntactic sugar for calling iterator(), then looping using hasNext() and next().
  • Iterator is 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() in Iterator
    • StreamSupport.stream() for Iterable
  • Java 9
    • Enhanced support for Spliterator API
  • Java 10+
    • var for inferring local variable types
  • Java 21
    • No direct changes to Iterable/Iterator, but better parallel stream support

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 than Collection<T>

Anti-Patterns

  • Returning null from next() — always throw NoSuchElementException if 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 Iterable for 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

  • Iterable and Iterator are 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

  1. Can I make my class Iterable and Serializable?
    Yes, they’re unrelated interfaces.

  2. Do I need both Iterable and Iterator interfaces?
    Yes — Iterable is the container, Iterator is the traversal mechanism.

  3. Can I use enhanced for-loop on my custom class?
    Yes, if it implements Iterable<T>.

  4. What happens if I call next() with no hasNext()?
    It should throw NoSuchElementException.

  5. Is my custom iterator reusable?
    No. Iterators are typically one-time use.

  6. Can I stop iteration early?
    Yes — break the loop or filter with Streams.

  7. Is it OK to return null in next()?
    Discouraged. Use exceptions for end-of-iteration.

  8. Are custom Iterators thread-safe?
    Not by default — add synchronization if needed.

  9. Can I create an infinite iterator?
    Yes, but be careful — always use with a limit condition.

  10. Should I use Iterable for APIs or Iterator?
    Prefer returning Iterable<T> — it's more flexible.