Definition
- Stream.generate(Supplier) creates an infinite, unordered stream by repeatedly calling the supplier.
- limit(n) is a short-circuiting operation that stops the stream after n elements.
- Without limit, any terminal operation would run forever.
- Unordered nature: the supplier has no notion of position; values are independent.
- For reproducible tests, use a seeded
Randominstead ofMath.random().
Priya at National Bank of India has a machine that prints a random salary amount on demand, forever. She presses the button but tells it to stop after ten prints, giving her exactly ten synthetic salary values for a load test.
Understanding the Concepts
What is Stream.generate?
Stream.generate(Supplier creates an infinite stream by calling the supplied function each time an element is demanded. The stream never ends on its own: it keeps calling the supplier for as long as elements are requested. You must bound it with a short-circuiting operation like limit(n) before any terminal operation.
How do you generate random salaries?
package com.bank.hr;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.Random;
import java.util.stream.Stream;
public class RandomSalaryGenerator {
public static void main(String[] args) {
// Generate 10 random salaries between 500,000 and 2,000,000
Random rng = new Random(42); // seeded for reproducibility
List<BigDecimal> salaries = Stream.generate(
() -> BigDecimal.valueOf(500_000 + rng.nextInt(1_500_001))
.setScale(2, RoundingMode.HALF_UP))
.limit(10)
.toList();
salaries.forEach(s -> System.out.println("Rs." + s));
}
}
Rs.1630289.00
Rs.1539046.00
Rs.689702.00
Rs.1485995.00
Rs.1230290.00
Rs.867810.00
Rs.1953402.00
Rs.1108204.00
Rs.1365866.00
Rs.529511.00
Why is limit mandatory for infinite streams?
Without limit or another short-circuiting operation (findFirst, anyMatch), the stream would call the supplier indefinitely when a terminal operation pulls elements. The program would never finish.
// WRONG: no limit on an infinite stream
Stream.generate(Math::random)
.forEach(System.out::println); // prints forever, never terminates
// CORRECT: bound with limit
Stream.generate(Math::random)
.limit(10)
.forEach(System.out::println); // prints exactly 10 values
How is generate different from iterate?
| Aspect | Stream.generate(supplier) | Stream.iterate(seed, f) |
|---|---|---|
| Value dependency | Independent: no relation between values | Dependent: each value from the previous |
| Ordering | Unordered | Ordered (sequential) |
| Use case | Random values, constant values | Sequences (Fibonacci, powers, counters) |
| Parallel safety | Supplier must be thread-safe | Safe if function is pure |
What are common mistakes?
Mistake: Forgetting to bound the infinite stream.
// WRONG: forEach on an infinite stream never terminates
Stream.generate(() -> new BigDecimal("1000000"))
.forEach(System.out::println); // runs forever
// CORRECT: always limit infinite streams
Stream.generate(() -> new BigDecimal("1000000"))
.limit(5)
.forEach(System.out::println); // prints 5 times
What about reproducible randomness?
For unit tests, use a seeded Random so the same seed produces the same sequence on every run.
Random seeded = new Random(12345);
List<Double> reproducible = Stream.generate(seeded::nextDouble)
.limit(5)
.toList();
// Same output every run with seed 12345
Math.random() uses a shared, unseeded Random and produces different values each run.
What about parallelizing a generate stream?
- Avoid parallelizing
Stream.generatebecause: - The supplier may not be thread-safe (e.g.,
Math.random()uses a sharedRandom). - The stream is unordered, so ordering is meaningless.
- There is no efficient way to split an infinite supplier.
What should a senior engineer know?
Choose generate for stateless, independent values like random numbers, timestamps, or constant fill values. Choose iterate for sequences where each value depends on the previous. Always bound infinite streams with limit. Use seeded Random for reproducible tests.
What are common interview follow-up questions?
- "What happens without limit?" The terminal operation runs forever.
- "How do you make it reproducible?" Use a seeded
Random. - "Can you parallelize generate?" Not recommended: supplier thread safety and no efficient splitting.
- "What is the difference from iterate?" Generate is stateless/independent; iterate is stateful/dependent.
Visual Overview
Stream.generate(supplier)
│
│ supplier() → val1 (infinite)
│ supplier() → val2
│ supplier() → val3
│ ...forever...
│
│ limit(10) ← bounds the stream
▼
[val1, val2, val3, ..., val10] ← exactly 10 elements
│
│ terminal operation (toList, forEach, etc.)
▼
Result
Key Takeaway
Stream.generate takes a supplier and produces an infinite stream, calling the supplier each time an element is demanded. Because it never ends on its own, you must bound it with a short-circuiting operation like limit before any terminal operation. generate(random).limit(10) gives exactly ten synthetic values.
At the National Bank of India, Rajesh uses this to seed load tests with random salary data. He uses a seeded Random for reproducibility so the same seed generates the same test data on every run.
The key discipline is always bounding infinite streams. The choice between generate and iterate depends on whether values are independent (use generate) or sequential with each depending on the previous (use iterate). Never parallelize a generate stream because the supplier may not be thread-safe and there is no efficient way to split it.
Remember one sentence: Stream.generate(supplier).limit(n) produces n independent values from an infinite source, but always bound it with limit or the pipeline runs forever.
---