Intermediate vs Terminal Operations
Every stream operation is either intermediate or terminal. An intermediate operation, such as filter or map, returns a new stream, so more operations can be chained after it. A terminal operation, such as toList or count, produces a result (or a side effect) and ends the stream. Knowing which is which explains why a pipeline without a terminal operation does nothing at all. In this lesson, you will learn the differences and the full list of each kind.
Differences Between Intermediate and Terminal Operations
| Intermediate | Terminal | |
|---|---|---|
| Returns | A new Stream | A result: a value, a collection, Optional, or void |
| Can be chained | Yes | No; it ends the pipeline |
| When it runs | Lazily: only when a terminal operation starts | Immediately: it starts the whole pipeline |
| How many per pipeline | Zero or more | Exactly one |
| Examples | filter, map, sorted | toList, collect, count, forEach |
Intermediate Operations
| Method | Description |
|---|---|
filter(Predicate) | It keeps the elements that pass a test. |
map(Function) | It transforms each element. |
mapToInt / mapToLong / mapToDouble | It transforms into a primitive stream. |
flatMap(Function) | It replaces each element with a stream and joins them. |
distinct() | It removes duplicates. |
sorted() / sorted(Comparator) | It sorts the elements. |
peek(Consumer) | It looks at each element as it passes (for debugging). |
limit(n) / skip(n) | It keeps the first n or skips the first n. |
takeWhile / dropWhile (Java 9) | It keeps or drops elements while a test is true. |
boxed() | It turns a primitive stream into a stream of wrappers. |
Terminal Operations
| Method | Description |
|---|---|
toList() (Java 16) | It collects into an unmodifiable list. |
collect(Collector) | It collects into a list, set, map, string and more. |
forEach(Consumer) | It performs an action for each element. |
count() | It returns the number of elements. |
reduce(...) | It combines all elements into one value. |
min(Comparator) / max(Comparator) | It returns the smallest or largest, as an Optional. |
findFirst() / findAny() | It returns an element, as an Optional. |
anyMatch / allMatch / noneMatch | It tests the elements and returns a boolean. |
toArray() | It collects into an array. |
sum(), average() | It totals or averages a primitive stream. |
Example: A Pipeline Without a Terminal Operation
Let's see an example that proves intermediate operations wait for a terminal one.
import java.util.List;
import java.util.stream.Stream;
public class NothingHappens {
public static void main(String[] args) {
List<String> names = List.of("Navika", "Srihitha", "Advitha");
Stream<String> pipeline = names.stream()
.filter(n -> {
System.out.println(" filter " + n);
return n.length() > 6;
});
System.out.println("Pipeline built, nothing printed yet");
long count = pipeline.count();
System.out.println("Count: " + count);
}
}Pipeline built, nothing printed yet
filter Navika
filter Srihitha
filter Advitha
Count: 2Explanation: Building the pipeline with filter printed nothing, because intermediate operations are lazy. Only when count() (a terminal operation) ran did the elements flow through the filter.
Example: Short-Circuiting Terminal Operations
Some terminal operations stop as soon as they know the answer:
import java.util.List;
public class ShortCircuit {
public static void main(String[] args) {
List<Integer> marks = List.of(91, 38, 85, 42);
System.out.println("Any fail? "
+ marks.stream().anyMatch(m -> m < 40));
System.out.println("All pass? "
+ marks.stream().allMatch(m -> m >= 40));
System.out.println("None above 100? "
+ marks.stream().noneMatch(m -> m > 100));
System.out.println("First above 80: "
+ marks.stream().filter(m -> m > 80).findFirst().get());
}
}Any fail? true
All pass? false
None above 100? true
First above 80: 91Explanation: anyMatch stops at 38, the first failing mark. allMatch also stops at 38, because one failure is enough to answer "no". findFirst stops at 91 without looking further.
Common Mistakes
Building a pipeline and forgetting the terminal operation. Nothing runs.
Using peek to change data. It's for looking only; use map to transform.
Calling get() on the Optional from findFirst without checking. It throws if the stream was empty.
Interview Corner
What is the difference between intermediate and terminal operations?
Intermediate operations, such as filter, map, sorted and distinct, return a new stream and are lazy: they only describe work. Terminal operations, such as collect, toList, forEach, count, reduce and findFirst, produce a result or side effect, trigger the processing of the whole pipeline, and consume the stream so it can't be reused. A pipeline has any number of intermediate operations and exactly one terminal operation.
Practice
- Easy: Classify each as intermediate or terminal:
limit,max,distinct,collect,skip,anyMatch. - Harder: Add
peekcalls to a pipeline offilter,mapandfindFirstand print what passes each stage. Explain why only some elements are printed.
Key Takeaways
Was this tutorial helpful?