The Streams API

Creating Streams

15 min Lesson 2 of 13

Creating Streams

Before you can process data with the Streams API you need a stream. Java gives you several factory methods and source types, each suited to a different situation. Knowing which entry-point to reach for — and why — makes your code cleaner than littering it with manual loops from day one.

Why does the source matter?

A Stream<T> is a lazy, single-use pipeline. It does not hold data; it describes how data will be pulled from a source and transformed. The source determines the element type, the order guarantee, and sometimes whether the stream can run in parallel efficiently. Choosing the right factory is therefore not cosmetic — it affects correctness and performance.

Streams from Collections

The most common source is any Collection (a List, Set, or Queue). Every collection inherits stream() and parallelStream() from java.util.Collection:

import java.util.List; import java.util.Set; import java.util.stream.Stream; List<String> names = List.of("Alice", "Bob", "Carol"); // sequential stream — elements arrive in list order Stream<String> sequential = names.stream(); // parallel stream — order is NOT guaranteed Stream<String> parallel = names.parallelStream(); sequential.forEach(System.out::println); // Alice Bob Carol (in order)
Order guarantee: List.stream() preserves insertion order. Set.stream() does not — a HashSet has no defined order. If order matters, prefer a List or LinkedHashSet.

Streams from Arrays

When your data lives in an array, use Arrays.stream(). It also accepts a range so you can stream a sub-array without copying:

import java.util.Arrays; int[] scores = {88, 72, 95, 60, 81}; // stream the whole array Arrays.stream(scores).forEach(System.out::println); // stream only indices 1..3 (inclusive start, exclusive end) Arrays.stream(scores, 1, 4).forEach(System.out::println); // 72 95 60

Arrays.stream(int[]) returns an IntStream, not a Stream<Integer>. This is intentional — primitive streams avoid boxing overhead and are covered in depth in Lesson 8.

Stream.of — Inline Elements

Stream.of() builds a stream directly from a handful of values without creating a collection first. It is ideal for tests, quick prototypes, and times when you already have individual references:

import java.util.stream.Stream; // varargs — works for any number of elements Stream<String> colours = Stream.of("red", "green", "blue"); colours.forEach(System.out::println); // single element Stream<Integer> one = Stream.of(42); // empty stream (a valid, useful stream that emits nothing) Stream<String> empty = Stream.empty();
Prefer Stream.empty() over Stream.of() with no arguments when your intent is an empty stream. The empty factory makes the meaning explicit and avoids an unchecked generic warning.

Stream.generate — Infinite Supplier-based Streams

Stream.generate(Supplier<T>) creates an infinite stream by calling a Supplier repeatedly. Because it never terminates on its own you must add a short-circuit operation such as limit():

import java.util.UUID; import java.util.stream.Stream; // generate 5 random UUIDs Stream.generate(() -> UUID.randomUUID().toString()) .limit(5) .forEach(System.out::println); // generate a constant stream (useful in testing) Stream.generate(() -> "ping") .limit(3) .forEach(System.out::println); // ping ping ping

The supplier has no memory of previous calls — each invocation is independent. If you need elements that depend on their position or on the previous value, use Stream.iterate instead.

Stream.iterate — Infinite Sequence-based Streams

Stream.iterate has two overloads:

  • Two-arg (Java 8+): Stream.iterate(seed, UnaryOperator) — infinite, terminated by limit() or takeWhile().
  • Three-arg (Java 9+): Stream.iterate(seed, Predicate, UnaryOperator) — built-in stop condition, works like a for loop.
import java.util.stream.Stream; // two-arg: even numbers 0, 2, 4, 6, 8 Stream.iterate(0, n -> n + 2) .limit(5) .forEach(System.out::println); // three-arg (Java 9): identical result, no limit() needed Stream.iterate(0, n -> n < 10, n -> n + 2) .forEach(System.out::println); // fibonacci sequence (first 10 terms) using an array to carry state Stream.iterate(new long[]{0, 1}, f -> new long[]{f[1], f[0] + f[1]}) .limit(10) .map(f -> f[0]) .forEach(System.out::println);
Never call a terminal operation on an infinite stream without a short-circuit guard. Calling collect() or count() on an unbounded generate or two-arg iterate stream will run forever (or until the JVM runs out of memory). Always pair them with limit(n) or takeWhile(predicate).

Choosing the Right Factory

  • You have a List or Set? Use .stream().
  • You have an existing array? Use Arrays.stream().
  • You have a small set of known values? Use Stream.of().
  • You need generated values with no state between calls? Use Stream.generate().
  • You need a sequence where each element depends on the previous one? Use Stream.iterate().

Summary

Java provides five primary ways to create a stream: from a Collection, from an array via Arrays.stream(), from literal values via Stream.of(), and from infinite sources via Stream.generate() and Stream.iterate(). Each source type has a clear use-case. In the next lesson you will attach the first transformation stages — filter, map, and forEach — to the streams you create here.