iterate() and generate()

Build streams from a seed and a step function or from a supplier, stop them with a condition or a limit, and use them for sequences, retries and paging.

Signature
static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)
static <T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next)
static <T> Stream<T> generate(Supplier<? extends T> s)
  • Added in Java Java 8

Parameters

ParameterTypeDescription
$seedTThe first element of the stream.
$hasNextoptionalPredicate<? super T>Three-argument iterate only: tested before each element is emitted; the stream ends the first time it returns false.
$f / nextUnaryOperator<T>Computes the next element from the previous one.
$sSupplier<? extends T>generate only: called once for each element requested.

Return value

Stream<T>iterate: an ordered sequential stream of seed, f(seed), f(f(seed)) and so on, infinite unless hasNext is given. generate: an infinite sequential unordered stream of values from the supplier.

Errors

Nothing directly. Exceptions thrown by the supplied functions surface when elements are pulled.

Changelog

  • PHP Java 9Added iterate(seed, hasNext, next), which ends the stream when hasNext returns false, like a for loop.

Most streams come from a collection. iterate and generate create a stream from a rule instead, and both can be infinite, which is only useful together with something that stops them.

iterate

Stream.iterate(seed, f) produces seed, then f(seed), then f(f(seed)), without end. Pair it with limit, takeWhile or a short-circuiting terminal operation such as findFirst or anyMatch.

Java 9 added Stream.iterate(seed, hasNext, next), which reads like a for loop: Stream.iterate(1, n -> n <= 100, n -> n * 10) gives 1, 10, 100. The condition is checked before each element, including the seed, so a seed that fails gives an empty stream. IntStream.iterate and LongStream.iterate have the same overloads without boxing.

Each element depends on the previous one, so iterate is inherently sequential and gains nothing from parallel(). For a plain range of numbers, IntStream.range(0, n) is clearer and splits well.

generate

Stream.generate(supplier) calls the supplier for every element. Each element is independent of the others, which suits constant values, fresh objects, and values from a generator such as a sequence counter. The stream is unordered, which makes no difference sequentially but lets a parallel stream take values in any order.

The two mistakes

  • No stopping condition. Stream.generate(...).toList() or .sorted() on an infinite stream never finishes, and eventually fails with OutOfMemoryError. limit must come before any operation that needs all elements.
  • A condition that cannot end it. filter does not stop a stream. Stream.iterate(1, n -> n + 1).filter(n -> n < 10).toList() runs forever, because filter keeps checking. Use takeWhile or the three-argument iterate.

Back-end uses

Exponential backoff delays are a clean iterate: start at 200 ms and double with a cap. Cursor-based paging through a remote API is another: start from the first page, compute the next from the cursor, and stop when there is none, then flatMap the items. For test data, generate with a counter or a seeded Random gives repeatable fixtures. Avoid unseeded randomness and the current time in tests that compare output.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Compute the delay schedule for retrying a failed call to a payment provider.
  • Walk a cursor-paginated third-party API page by page until it reports no next cursor.
  • Generate a fixed number of fake customers with predictable IDs for an integration test.

Frequently asked questions

How do I stop an infinite stream in Java?

Use limit(n), takeWhile(predicate) or a short-circuiting terminal operation such as findFirst or anyMatch. On Java 9 and later, Stream.iterate(seed, hasNext, next) has the stopping condition built in. filter does not stop an infinite stream.

What is the difference between Stream.iterate and Stream.generate?

iterate computes each element from the previous one, starting at a seed, so it is ordered and sequential. generate calls a Supplier independently for each element and produces an unordered stream.

How do I create a list of N identical or generated values?

Stream.generate(supplier).limit(n).toList() calls the supplier n times. For identical values Collections.nCopies(n, value) is simpler; for numbered values IntStream.range(0, n).mapToObj(...) is clearer.

More examples

Exponential backoff delays with a cap

Each delay is derived from the previous one, which is exactly what iterate models. limit sets the maximum number of attempts, and the cap stops the delay from growing without bound.

Example · java
void main() {
    List<Integer> delaysMs = Stream.iterate(200, d -> Math.min(d * 2, 5000))
        .limit(7)
        .toList();
    IO.println(delaysMs);

    int totalWait = delaysMs.stream().mapToInt(Integer::intValue).sum();
    IO.println("worst case wait: " + totalWait + " ms");
}
// Output:
// [200, 400, 800, 1600, 3200, 5000, 5000]
// worst case wait: 16200 ms

Following cursors through a paged API

The three-argument iterate fetches the next page only after the previous one has been consumed and stops when there is no next cursor. Here the map stands in for an HTTP client so the program runs offline.

Example · java
record Page(int number, List<String> items, Integer next) {}

Map<Integer, Page> api = Map.of(
    1, new Page(1, List.of("a", "b"), 2),
    2, new Page(2, List.of("c", "d"), 3),
    3, new Page(3, List.of("e"), null));

Page fetch(int number) {
    IO.println("GET /items?page=" + number);
    return api.get(number);
}

void main() {
    List<String> all = Stream.iterate(fetch(1), p -> p != null, p -> p.next() == null ? null : fetch(p.next()))
        .flatMap(p -> p.items().stream())
        .toList();
    IO.println(all);
}
// Output:
// GET /items?page=1
// GET /items?page=2
// GET /items?page=3
// [a, b, c, d, e]

Repeatable test data with generate

generate with a counter produces fixtures that are the same on every run, so assertions stay stable. IntStream.iterate with a condition is the stream form of a for loop over page offsets.

Example · java
record Customer(int id, String email) {}

void main() {
    AtomicInteger ids = new AtomicInteger();
    List<Customer> customers = Stream.generate(() -> {
            int id = ids.incrementAndGet();
            return new Customer(id, "customer" + id + "@example.com");
        })
        .limit(3)
        .toList();
    customers.forEach(IO::println);

    IntStream.iterate(0, offset -> offset < 10, offset -> offset + 4)
        .forEach(offset -> IO.println("offset " + offset));
}
// Output:
// Customer[id=1, [email protected]]
// Customer[id=2, [email protected]]
// Customer[id=3, [email protected]]
// offset 0
// offset 4
// offset 8

Discussion

  • Be the first to comment on this lesson.