Back to Stream Creation & Utilities
Q8Question 8 of 18

Generate a fixed number of unique transaction reference numbers using Stream.generate

Key Concepts
  • Stream.generate(supplier) builds an infinite stream by calling the supplier for each element, ignoring any previous value
  • Stream.generate(() -> "TXN-" + UUID.randomUUID()).limit(n) yields n unique transaction references
  • generate has no seed and each element is independent, unlike iterate which derives each value from the previous one
  • The stream is infinite because the supplier can be called endlessly, so limit is mandatory to bound it
  • limit short-circuits a lazy infinite stream, calling the supplier exactly n times
  • generate is unordered, so it parallelizes better than the ordered, dependent iterate
  • Use generate for independent values like random IDs or constants, and iterate for dependent sequences

Definition

Stream.generate(supplier) produces an infinite stream by calling the supplier for each element with no dependence on the previous value, so limit(n) bounds it to exactly n values, such as n unique transaction references.

Stream.generate builds a stream from a supplier that is called once per element.

  • Stream.generate(supplier) – each element comes from calling supplier.get(), independent of any other element.
  • No seed – unlike iterate, there is no starting value and no dependence on the previous element.
  • Infinite by design – the supplier can be called forever, so the stream never ends on its own.
  • limit(n) – a short-circuiting operation that calls the supplier exactly n times and then stops.
  • Unordered – because elements do not depend on order, generate parallelizes more easily than iterate.

At the National Bank of India, Rajesh runs a payment batch and needs a fresh reference number for each transaction. He walks to a ticket machine that prints a new unique ticket every time he presses the button. The machine does not care what the last ticket said; each press is independent. Stream.generate is that ticket machine, able to print forever. Rajesh needs five references for this batch, so he presses the button five times with limit(5) and walks away.

Understanding the Concepts

What is Stream.generate?

Stream.generate creates a stream whose elements all come from a single supplier. For each element it calls supplier.get() and uses the result. The supplier takes no input and does not see the previous element, so every element is produced independently.

JAVA
package com.bank.payment;

import java.util.stream.Stream;

public class GenerateBasics {
    public static void main(String[] args) {
        // Supplier returns a constant; generate repeats it.
        Stream.generate(() -> "PING")
                .limit(3)
                .forEach(System.out::println);
    }
}
TEXT
PING
PING
PING

How do you generate unique transaction reference numbers?

Use a supplier that returns a fresh unique value each call, such as a UUID-based reference. limit(n) fixes the batch size.

JAVA
package com.bank.payment;

import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class ReferenceGenerator {
    public List<String> generateReferences(int count) {
        return Stream.generate(() -> "TXN-" + UUID.randomUUID())
                .limit(count)
                .collect(Collectors.toList());
    }

    public static void main(String[] args) {
        new ReferenceGenerator().generateReferences(5)
                .forEach(System.out::println);
    }
}
TEXT
TXN-5f2b1c9a-7d33-4e21-9b0a-1c2d3e4f5a6b
TXN-a1b2c3d4-e5f6-4789-90ab-cdef01234567
TXN-9988aa77-6655-4433-2211-00ffeeddccbb
TXN-13579bdf-2468-4ace-8135-79bdf2468ace
TXN-0a1b2c3d-4e5f-4061-8273-849506172839

Each UUID is unique, so every reference in the batch is distinct.

Why is Stream.generate infinite?

There is no stopping condition in generate. The supplier can be called an unlimited number of times, so the stream describes an endless source. You decide how much of it to consume with limit.

JAVA
// WRONG: no bound, the supplier is called forever
Stream.generate(() -> "TXN-" + java.util.UUID.randomUUID())
        .forEach(System.out::println); // never terminates
JAVA
// CORRECT: limit(5) calls the supplier five times, then stops
Stream.generate(() -> "TXN-" + java.util.UUID.randomUUID())
        .limit(5)
        .forEach(System.out::println);

The wrong version prints references until you kill the process. The correct version stops at five.

How does limit bound the infinite stream?

limit(n) is a short-circuiting intermediate operation. Because the stream is lazy, the supplier is called only when limit requests another element, and limit requests exactly n of them.

JAVA
package com.bank.payment;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;

public class SupplierCallCount {
    public static void main(String[] args) {
        AtomicInteger calls = new AtomicInteger();
        Stream.generate(() -> {
                    calls.incrementAndGet();
                    return "TXN";
                })
                .limit(4)
                .forEach(v -> {});
        System.out.println("supplier called " + calls.get() + " times");
    }
}
TEXT
supplier called 4 times

The supplier runs exactly four times, proving limit bounds the lazy source precisely.

How does Stream.generate differ from Stream.iterate?

This is the core comparison. generate calls a supplier that ignores the previous value, so each element is independent. iterate applies a function to the previous value, so each element depends on the one before it.

| Aspect | Stream.generate | Stream.iterate |
|--------|------------------|-----------------|
| Source | Supplier | seed + UnaryOperator |
| Depends on previous | no | yes |
| Has a seed | no | yes |
| Ordered | unordered | ordered |
| Parallelizes | yes, elements independent | poorly, dependent chain |
| Typical use | random IDs, constants, sensor reads | dates, balances, counters |
| Bounding | limit | limit or 3-arg predicate |

JAVA
package com.bank.payment;

import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Stream;

public class GenerateVsIterate {
    public static void main(String[] args) {
        System.out.println("generate (independent):");
        Stream.generate(() -> ThreadLocalRandom.current().nextInt(1000))
                .limit(3)
                .forEach(System.out::println);

        System.out.println("iterate (dependent):");
        Stream.iterate(1000, n -> n + 1)
                .limit(3)
                .forEach(System.out::println);
    }
}
TEXT
generate (independent):
527
83
914
iterate (dependent):
1000
1001
1002

What are common mistakes with Stream.generate?

The most common mistake is omitting limit, which hangs. A subtler mistake is relying on generate to produce a counting or dependent sequence, which it cannot do because the supplier has no memory of the previous value.

JAVA
// WRONG: trying to count with generate; the supplier cannot see the last value
int[] n = {0};
Stream.generate(() -> n[0]++)   // works only via an external mutable hack
        .limit(3)
        .forEach(System.out::println);
JAVA
// CORRECT: for a counting sequence use iterate, which is built for it
Stream.iterate(0, i -> i + 1)
        .limit(3)
        .forEach(System.out::println);

The wrong version leans on a shared mutable array, which breaks under parallelism. For a dependent sequence, iterate is the right tool.

How do you guarantee uniqueness across a batch?

UUIDs are practically unique, but if you need a readable sequence you can combine generate with an atomic counter wrapped in the supplier, or better, use a thread-safe source. Here is a readable reference with a timestamp prefix and a counter.

JAVA
package com.bank.payment;

import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class ReadableReferences {
    public List<String> batch(int count) {
        AtomicLong seq = new AtomicLong(1);
        String day = "20240115";
        return Stream.generate(() -> "TXN-" + day + "-" + seq.getAndIncrement())
                .limit(count)
                .collect(Collectors.toList());
    }

    public static void main(String[] args) {
        new ReadableReferences().batch(4).forEach(System.out::println);
    }
}
TEXT
TXN-20240115-1
TXN-20240115-2
TXN-20240115-3
TXN-20240115-4

The AtomicLong keeps the counter thread-safe even if the stream is parallelized.

What are the edge cases?

A limit(0) yields an empty stream and never calls the supplier. A supplier that returns null produces a stream of nulls, which usually signals a bug downstream.

JAVA
package com.bank.payment;

import java.util.stream.Stream;

public class EdgeCases {
    public static void main(String[] args) {
        long zero = Stream.generate(() -> "TXN").limit(0).count();
        System.out.println("limit(0) count = " + zero);

        // A null-returning supplier is legal but risky.
        Stream.generate(() -> (String) null).limit(2)
                .forEach(v -> System.out.println("value = " + v));
    }
}
TEXT
limit(0) count = 0
value = null
value = null

What about thread safety and parallelism?

Because elements are independent, generate can run in parallel, but the supplier itself must be thread-safe. A plain Random shared across threads contends, so prefer ThreadLocalRandom or an atomic counter.

JAVA
package com.bank.payment;

import java.util.UUID;
import java.util.stream.Stream;

public class ParallelGenerate {
    public static void main(String[] args) {
        // UUID.randomUUID() is thread-safe, so parallel is fine here.
        long distinct = Stream.generate(() -> UUID.randomUUID().toString())
                .limit(10_000)
                .parallel()
                .distinct()
                .count();
        System.out.println("distinct references = " + distinct);
    }
}
TEXT
distinct references = 10000

When should you use Stream.generate?

Use generate when each element is independent of the others: unique IDs, random samples, constant fill values, mock test data, or repeated sensor reads. Use iterate instead when each element is computed from the previous one.

JAVA
package com.bank.payment;

import java.math.BigDecimal;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class MockData {
    public static void main(String[] args) {
        // Fill a batch of pending payments with a constant placeholder amount.
        List<BigDecimal> pending = Stream.generate(() -> new BigDecimal("0.00"))
                .limit(3)
                .collect(Collectors.toList());
        System.out.println(pending);
    }
}
TEXT
[0.00, 0.00, 0.00]

What are common interview follow-up questions?

Interviewers ask you to contrast generate with iterate on dependence and ordering, explain why both are infinite, describe how limit short-circuits a lazy stream, and discuss thread safety of the supplier under parallel(). A strong answer: generate is a seedless, independent, unordered source good for IDs and random data, iterate is a seeded, dependent, ordered source good for schedules and counters, and both rely on laziness plus limit to stay bounded.

Visual Overview

DIAGRAM
  Stream.generate(supplier)

   supplier.get() ─► element 1
   supplier.get() ─► element 2
   supplier.get() ─► element 3
   supplier.get() ─► ...  (never ends on its own)

  add .limit(5)

   get ─► get ─► get ─► get ─► get  ╳ STOP
   (limit stops the source after 5 supplier calls)
DIAGRAM
  generate vs iterate

  generate(supplier)                 iterate(seed, f)
   each element independent            each element from previous
        │                                   │
   ┌────┴────┐                         seed ─► f ─► f ─► f ...
   get  get  get                            (ordered, dependent)
   (unordered)
DIAGRAM
  Transaction reference batch

  () -> "TXN-" + UUID.randomUUID()
        │ limit(5)
        ▼
  TXN-5f2b...
  TXN-a1b2...
  TXN-9988...
  TXN-1357...
  TXN-0a1b...   ◄── 5th call, limit stops here

Key Takeaway

At the National Bank of India, Rajesh runs a payment batch and needs a fresh reference number for every transaction. He uses a ticket machine that prints a new unique ticket on each press, never caring what the previous ticket said. Stream.generate is that ticket machine: it calls a supplier for each element, and the supplier produces its value independently, with no seed and no memory of the past.

The stream is infinite because the supplier can be called endlessly. That is safe only because streams are lazy, so the supplier runs only when something downstream asks for another element. When Rajesh adds limit(5), the pipeline calls the supplier exactly five times and stops, giving him a fixed batch of unique references built from UUIDs.

The key contrast with iterate is dependence. generate elements are independent and unordered, which is why it parallelizes well as long as the supplier is thread-safe. iterate elements each depend on the previous one, which makes it ordered, sequential, and the right choice for dates, balances, and counters. Trying to count with generate forces an ugly external mutable hack; that job belongs to iterate.

The traps are forgetting limit, which hangs, returning null from the supplier, and sharing a non-thread-safe supplier under parallel(). For unique IDs, random samples, and constant fills, generate is the clean tool.

Remember one sentence: Stream.generate calls a supplier for each independent element and runs forever, so limit fixes the count, while iterate is for sequences where each value depends on the last.

---

Share
Use ← → arrow keys to navigate