ZetCode

Java Stream generate

last modified May 25, 2025

This article demonstrates how to use the Java Stream generate method to create infinite streams of data.

Stream.generate is a static factory method that creates an infinite, unordered stream where each element is generated by the provided Supplier. This method is useful for creating streams of random data, constant values, or stateful generators.

The generate method is part of the Java Stream API, introduced in Java 8, and it allows for lazy evaluation of elements. This means that elements are only generated when they are needed, which can be efficient for large or infinite streams.

The method signature for Stream.generate is:

static <T> Stream<T> generate(Supplier<? extends T> s)

The Supplier is a functional interface that provides elements of type T. The resulting stream is infinite, so it typically needs to be limited using operations like limit. The Supplier function is called each time an element is requested, allowing for dynamic generation of values.

Generating constant values

Creating a stream of constant values.

Main.java
void main() {

    Stream.generate(() -> "falcon")
          .limit(5)
          .forEach(System.out::println);
}

This example creates an infinite stream of "falcon" strings and limits it to 5 elements. The Supplier lambda () -> "falcon" provides the constant value.

$ java Main.java
falcon
falcon
falcon
falcon
falcon

Generating random numbers

We create a stream of random numbers.

Main.java
void main() {

    Random random = new Random();
    
    Stream.generate(random::nextDouble)
          .limit(5)
          .forEach(System.out::println);
}

This example generates random doubles between 0.0 and 1.0. The method reference random::nextDouble serves as the Supplier for the stream.

$ java Main.java
0.7310570414761102
0.2390504976470176
0.003338256382940902
0.9564232111672854
0.7865432763945762

Stateful generators

The generate method can also be used with stateful Suppliers, which maintain state between calls. This allows for generating sequences or dynamic values that depend on previous calls.

Main.java
void main() {

    AtomicInteger counter = new AtomicInteger(0);
    
    Stream.generate(() -> "Item " + counter.getAndIncrement())
          .limit(10)
          .forEach(System.out::println);
}

This example demonstrates a stateful generator using AtomicInteger to create sequentially numbered items. Note that stateful Suppliers should be used with caution in parallel streams.

$ java Main.java
Item 0
Item 1
Item 2
Item 3
Item 4
Item 5
Item 6
Item 7
Item 8
Item 9

Fibonacci sequence

The generate method can be used to create sequences like the Fibonacci sequence, where each element depends on the previous ones. This requires a custom Supplier that maintains state between calls.

Main.java
class FibonacciSupplier {

    private long prev = 0;
    private long curr = 1;
    
    public long get() {
        long next = prev + curr;

        prev = curr;
        curr = next;
        return prev;
    }
}

void main() {

    var fibSupplier = new FibonacciSupplier();
    
    Stream.generate(fibSupplier::get)
          .limit(10)
          .forEach(System.out::println);
}

This example generates the Fibonacci sequence using a custom stateful Supplier class. The Supplier maintains state between calls to generate the sequence.

$ java Main.java
1
1
2
3
5
8
13
21
34
55

Using AtomicLong

The following example demonstrates how to use AtomicLong with Stream.generate to create a sequence of numbers in a thread-safe manner. The AtomicLong class provides atomic operations to update a long value, which is particularly useful when managing state in streams, especially in concurrent scenarios.

Main.java
void main() {

    var val = new AtomicLong(2L);

    List data = Stream.generate(() -> val.getAndAdd(2))
            .limit(20)
            .toList();

    System.out.println(data);
}

The code above generates a list of 20 even numbers starting from 2 (i.e., [2, 4, 6, ..., 40]) by using AtomicLong to maintain state within the stream. The getAndAdd method atomically retrieves the current value and increments it by 2 for each element.

When working with state in Java streams, such as in the Stream.generate method, variables used within lambda expressions (like the supplier () -> val.getAndAdd(2)) must be final or effectively final. This restriction arises because lambda expressions in Java capture variables by reference, and Java enforces strict rules to prevent unintended side effects in concurrent or functional programming contexts.

Final or effectively final requirement means that a variable is final if it's explicitly declared with the final keyword, meaning it cannot be reassigned after initialization. A variable is effectively final if it's not declared final but is never reassigned after its initial value is set. Java requires variables used in lambda expressions (or anonymous classes) to be final or effectively final to ensure predictable behavior, especially in parallel streams where multiple threads might access the same variable.

In the example, the variable val is declared as var val = new AtomicLong(2L);. It is effectively final because it is not reassigned to a new AtomicLong object after initialization. The lambda expression () -> val.getAndAdd(2) captures val and uses it to generate stream elements. If you attempted to reassign val (e.g., val = new AtomicLong(10L);) later in the method, the lambda would fail to compile with an error like: "Variable used in lambda expression should be final or effectively final." This rule prevents accidental modifications that could lead to unpredictable results in stream processing.

While the val reference itself is effectively final, AtomicLong allows internal state changes through methods like getAndAdd. This is why AtomicLong is ideal for streams: it encapsulates mutable state in a thread-safe way without requiring reassignment of the val reference. Each call to getAndAdd(2) atomically updates the internal long value, generating the sequence [2, 4, 6, ...] without violating the final/effectively final rule.

If you tried using a regular long variable (e.g., long counter = 2;) and updated it within the lambda (e.g., () -> counter += 2), it would fail to compile because counter would no longer be effectively final. Modifying a non-final variable inside a lambda is not allowed. AtomicLong sidesteps this by managing the state internally while keeping the reference to the AtomicLong object unchanged.

Athough the example uses a sequential stream, AtomicLong ensures thread-safety if the stream is made parallel (e.g., Stream.generate(...).parallel()). The atomic operations prevent race conditions when multiple threads access and update val. Without AtomicLong, managing state in parallel streams would require complex synchronization, which is error-prone.

For simple arithmetic sequences like this, Stream.iterate(2L, n -> n + 2).limit(20) could be used, as it avoids explicit state management. However, AtomicLong with Stream.generate is more flexible for complex state updates or when thread-safety is needed.

$ java Main.java
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40]

Generating UUIDs

The next example demonstrates generating random UUIDs using the Stream.generate method. UUIDs (Universally Unique Identifiers) are commonly used for unique identifiers in distributed systems and databases.

Main.java
void main() {

    Stream.generate(UUID::randomUUID)
          .limit(3)
          .forEach(System.out::println);
}

This example generates random UUIDs using the UUID.randomUUID method as the Supplier. Each call to the Supplier produces a new random UUID.

$ java Main.java
a3e4f1b2-5c6d-4e3f-9g8h-7i6j5k4l3m2n
b4c5d6e7-f8g9-h0i1-j2k3-l4m5n6o7p8q9
c2d3e4f5-g6h7-i8j9-k0l1-m2n3o4p5q6r7

Simulating sensor data

The next example simulates sensor readings using the generate method. It demonstrates how to create a stream of sensor data with different types and distributions. This can be useful for testing applications that process sensor data or for generating realistic data for simulations.

Main.java
record SensorReading(String sensorId, double value, long timestamp) {}

void main() {

    Random random = new Random();
    String[] sensorIds = {"temp-1", "temp-2", "pressure-1", "humidity-1"};
    
    Stream.generate(() -> {
            String id = sensorIds[random.nextInt(sensorIds.length)];
            double value = switch(id.split("-")[0]) {
                case "temp" -> 20 + random.nextGaussian() * 5;
                case "pressure" -> 1000 + random.nextGaussian() * 10;
                case "humidity" -> 50 + random.nextGaussian() * 5;
                default -> 0;
            };
            return new SensorReading(id, value, System.currentTimeMillis());
        })
        .limit(8)
        .forEach(System.out::println);
}

This example simulates sensor readings with different distributions based on sensor type. The Supplier creates random values with realistic distributions for each sensor type.

$ java Main.java
SensorReading[sensorId=temp-2, value=18.463, timestamp=1625761234567]
SensorReading[sensorId=pressure-1, value=1005.382, timestamp=1625761234568]
SensorReading[sensorId=humidity-1, value=52.174, timestamp=1625761234569]
...

Combining with other operations

Using generate with other stream operations.

Main.java
void main() {

    var random = new Random();
    
    Stream.generate(() -> random.nextInt(100)):
          .filter(n -> n > 50)
          .distinct()
          .limit(5)
          .sorted()
          .forEach(System.out::println);
}

This example generates random numbers, filters for values greater than 50, removes duplicates, limits to 5 results, sorts them, and prints. It shows how generate can be combined with other stream operations.

$ java Main.java
53
67
72
84
91

Source

Java Stream generate documentation

In this article we have explored the Java Stream generate method. It provides a powerful way to create infinite streams of data, which can be useful for generating test data, simulations, and other scenarios where you need a continuous source of values.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Java tutorials.