Creating Java Streams
Create Java streams from collections, arrays, Stream.of, Stream.iterate, Stream.generate, and IO sources.
Creating Java Streams
The intro chapter showed the pipeline shape — source → intermediates → terminal — and treated the source as a given. This chapter is the catalogue of sources. Every stream pipeline you write begins with one of them, and each one has a small set of habits that decide whether the pipeline that follows is correct, lazy, finite, ordered, or parallelisable.
The list is shorter than it looks. Almost every stream you'll ever write starts with one of coll.stream(), Stream.of(...), Arrays.stream(arr), or an IntStream.range. The rest of this chapter is "and here are the few situations where the others are the right call."
From a Collection — coll.stream()
The dominant source. Collection<T> has a default stream() method, so every List, Set, Queue, and Deque exposes one for free:
List<String> names = List.of("Alice", "Bob", "Carol");
long count = names.stream().filter(n -> n.length() > 3).count();The stream is sequential, sized (the JVM knows the element count up front), and ordered if the collection is. A List produces an ordered stream; a HashSet produces an unordered one; a TreeSet produces an ordered one sorted by the set's comparator.
There's also coll.parallelStream(), which schedules across the common ForkJoinPool. Same source, different execution policy — covered in Java Parallel Streams.
From explicit elements — Stream.of(...)
Use Stream.of when you have a short, known list of elements and don't want a throwaway List:
Stream<String> s = Stream.of("a", "b", "c");
Stream<Integer> n = Stream.of(1, 2, 3, 4, 5);
Stream<Object> one = Stream.of("just one");It's a varargs method, so it takes any number of arguments (zero is allowed and yields an empty stream). With a single T[] argument the compiler picks Stream.of(T...), not Stream.of(T) — handy when you already have an array:
String[] arr = {"x", "y", "z"};
Stream<String> fromArr = Stream.of(arr); // same as Arrays.stream(arr)From an array — Arrays.stream(...)
Arrays.stream has overloads for T[], int[], long[], double[], plus range variants:
int[] xs = {3, 1, 4, 1, 5, 9, 2, 6};
IntStream ix = Arrays.stream(xs); // primitive specialisation
IntStream tail = Arrays.stream(xs, 2, xs.length); // half-open [2, len)
String[] words = {"alpha", "beta", "gamma"};
Stream<String> ws = Arrays.stream(words);The primitive overloads return IntStream, LongStream, DoubleStream — not Stream<Integer>. That matters: primitive streams skip boxing, have sum, average, min, max directly (no collector), and play well with mapToInt/mapToObj to move between worlds.
Primitive ranges — IntStream.range / rangeClosed
The fastest way to iterate by index without a for loop:
// 0, 1, 2, ..., 9
IntStream.range(0, 10).forEach(i -> System.out.println(i));
// 1..10 inclusive
int sum = IntStream.rangeClosed(1, 10).sum(); // 55range(a, b) is half-open [a, b). rangeClosed(a, b) is [a, b]. Both are bounded, ordered, sized, and faster than Stream.iterate(0, i -> i + 1).limit(n) because the JVM knows the count up front. Use them whenever a loop's body is "do something at index i."
To couple an index to a List's elements you write:
List<String> names = List.of("Alice", "Bob", "Carol");
IntStream.range(0, names.size())
.mapToObj(i -> i + ": " + names.get(i))
.forEach(System.out::println);Generated infinite streams — Stream.iterate and Stream.generate
Two ways to produce an unbounded stream. They look similar; they're not the same.
Stream.iterate(seed, f) — start with seed, then f(seed), then f(f(seed)), …. Ordered, deterministic, sequential. Almost always followed by a short-circuit:
Stream.iterate(1, n -> n * 2)
.limit(10)
.forEach(System.out::println); // 1, 2, 4, 8, ..., 512There's also a 3-arg overload Stream.iterate(seed, hasNext, next) (Java 9+) that bakes the stop condition into the source — no limit needed:
Stream.iterate(1, n -> n < 1000, n -> n * 2).forEach(System.out::println);Stream.generate(supplier) — calls a Supplier<T> repeatedly. Unordered, no relationship between elements:
Stream.generate(Math::random).limit(5).forEach(System.out::println);
Stream.generate(() -> "ping").limit(3).forEach(System.out::println);Use iterate for sequences where each term depends on the previous (n -> n + 1, n -> n * 2, the Fibonacci pair arr -> {arr[1], arr[0] + arr[1]}). Use generate for independent values from a side source — random numbers, fixed constants, UUIDs.
Either way, always terminate them with a short-circuiting operation: limit(n), the 3-arg iterate, or a terminal like findFirst / anyMatch. A plain toList() on an infinite stream hangs the JVM.
From I/O — Files.lines, BufferedReader.lines
Files.lines(path) opens a file and returns a Stream<String> of its lines. Lazy: lines are read as the pipeline pulls them, not up front:
try (Stream<String> lines = Files.lines(Path.of("words.txt"))) {
long longWords = lines.filter(w -> w.length() > 8).count();
}The try-with-resources is mandatory. The stream holds an open file handle, and the only way to release it is to call close() — which try-with-resources does for you. Without it the descriptor leaks until the stream is garbage-collected, which can be never under load.
Same shape for Readers via BufferedReader.lines(). Both are the canonical way to walk a text file without loading it into memory.
String.chars() and String.codePoints()
A String is a sequence of UTF-16 code units; the API exposes both views:
"hello".chars() // IntStream of UTF-16 code units
.filter(Character::isUpperCase)
.count();
"héllo".codePoints() // IntStream of Unicode code points
.mapToObj(Character::toString)
.forEach(System.out::println);Both return IntStream. chars() is fine for ASCII; for anything that might contain surrogate pairs (most emoji, many scripts), codePoints() is the safe choice.
Empty and single-element streams
For default cases and flatMap branches:
Stream<String> none = Stream.empty(); // 0 elements
Stream<String> one = Stream.of("x"); // exactly 1
Stream<String> opt = Optional.of("x").stream(); // 1 if present, else emptyOptional.stream() (Java 9+) is the bridge between Optional<T> and Stream<T> — handy when you flatMap a stream of Optionals into a stream of present values without any null bookkeeping.
Stream.Builder — pushing elements one at a time
When you can't express the source as a literal, an array, or a generator — usually because elements come from disparate branches of imperative code — there's a builder:
Stream.Builder<String> b = Stream.builder();
b.add("first");
if (someCondition) b.add("second");
b.accept("third");
Stream<String> s = b.build();After build() the builder is sealed; further add throws. It's a rare-but-honest tool. Most code that reaches for it is better written with an ArrayList<String> followed by list.stream(), but the builder avoids that intermediate when the data is built piecewise.
Map streams — there isn't one
Map<K, V> has no stream() method. You stream its views instead:
Map<String, Integer> ages = Map.of("Alice", 30, "Bob", 25);
ages.entrySet().stream().filter(e -> e.getValue() >= 18).map(Map.Entry::getKey).toList();
ages.keySet().stream().sorted().toList();
ages.values().stream().mapToInt(Integer::intValue).sum();entrySet().stream() is what you want most often — both halves of each entry are in scope, and Map.Entry::getKey / ::getValue work as method references.
Picking the right source
| Situation | Use |
|---|---|
You already have a List, Set, Queue | coll.stream() |
| You have a few fixed elements | Stream.of(a, b, c) |
You have a T[] | Arrays.stream(arr) |
You have int[], long[], double[] | Arrays.stream(arr) → primitive stream |
| You want to loop by index | IntStream.range(0, n) |
| You want each term from the previous | Stream.iterate |
| You want independent samples | Stream.generate |
| You want lines of a text file | Files.lines(path) inside try-with-resources |
You want characters of a String | "...".chars() or .codePoints() |
| You want a fallback empty stream | Stream.empty() |
| You're stuck building piecewise | Stream.builder() |
You want to stream a Map | map.entrySet().stream() |
That table covers everything in the chapter, and probably 99% of real code.
A worked example: ten sources, one program
The program below builds one stream from each of the major sources, runs a tiny terminal against it so the output is visible, and prints both the result and the kind of source it came from.
What to take from the run:
- Every line in the output came from a different source, but they all flow into the same intermediate/terminal vocabulary. The choice of source decides what the pipeline can start with; it does not change what comes after.
Arrays.stream(int[])produced anIntStream—sum()is right there on the stream, no boxing, noCollectors.summingInt. The primitive specialisations matter on numeric pipelines.- The two
Stream.iteratecalls show the difference betweeniterate(seed, f)+limit(n)(you pick the count) and the 3-argiterate(seed, hasNext, next)(the source picks the count). Both are bounded; aniteratewithout a bound and without a short-circuiting terminal is the classic JVM-hangs-forever bug. Stream.empty()andOptional.of(...).stream()are how empty and single-element streams enter a pipeline — typically inside aflatMapbranch where some inputs produce zero or one downstream element.Stream.builder()is the escape hatch for the (rare) case where the source is built imperatively across branches. Most real code reaches forcoll.stream()first.
What's next
You can now build any stream you need from any source you actually have. The next two chapters cover the operations that run between the source and the result. First, Java Stream Intermediate Operations — filter, map, flatMap, distinct, sorted, peek, limit, skip — the lazy transformations that reshape the stream without running it. Then the terminals that produce the value.
Practice
Which of these is the *cheapest* way to iterate the integers `0` through `99`, in order, as a stream?