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.
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
| Parameter | Type | Description |
|---|---|---|
$seed | T | The first element of the stream. |
$hasNextoptional | Predicate<? super T> | Three-argument iterate only: tested before each element is emitted; the stream ends the first time it returns false. |
$f / next | UnaryOperator<T> | Computes the next element from the previous one. |
$s | Supplier<? 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 withOutOfMemoryError.limitmust come before any operation that needs all elements. - A condition that cannot end it.
filterdoes not stop a stream.Stream.iterate(1, n -> n + 1).filter(n -> n < 10).toList()runs forever, because filter keeps checking. UsetakeWhileor the three-argumentiterate.
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
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.
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 msFollowing 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.
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.
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