Java Streams: The Pipeline Model
Java Streams: The Pipeline Model
Introduced in Java 8, the Streams API offers a functional way to process sequences of data. A stream is not a data structure; it is a lazily evaluated pipeline that pulls elements from a source, transforms them through intermediate operations, and produces a result with exactly one terminal operation. Thinking in pipelines changes how you write data-processing code.
Sources and the pipeline
Streams come from collections via stream() or parallelStream(), from arrays, from Stream.of, or from range generators like IntStream.range. The classic pipeline has three stages: a source, zero or more intermediate operations, and exactly one terminal operation. Intermediate steps are lazy and compose naturally.
List<String> words = List.of("stream", "lazy", "eager", "filter");
long count = words.stream()
.filter(w -> w.length() > 4)
.map(String::toUpperCase)
.count();
System.out.println(count);
Laziness and intermediate operations
Intermediate operations such as filter, map, distinct, sorted, and limit return new streams and do no work until a terminal operation runs. This laziness enables short-circuiting: findFirst on a filtered stream stops as soon as it has a match, so you do not pay for processing the whole source.
Terminal operations
Terminal operations produce a result and consume the stream: collect, toList, count, anyMatch, forEach, and reduce. After a terminal operation the stream is exhausted and cannot be reused. Streams are single-use by design, which keeps behavior predictable.
Streams are not collections
Write stream.toList() or collect(Collectors.toList()) to materialize results back into a collection. Keep streams for transformations and aggregation rather than storing state. If your pipeline mutates external state, you are probably fighting the model rather than using it.
Key Points
- A stream is a lazy pipeline, not a container.
- Pipelines consist of a source, intermediate operations, and one terminal operation.
- Intermediate operations are lazy and enable short-circuiting.
- Terminal operations consume the stream; streams are single-use.
- Use
stream(),IntStream.range, andStream.ofto create sources.