liveBook · Manning

Terminal operation

As explained in 100 Java Mistakes and How to Avoid Them

A terminal operation is a stream operation that triggers evaluation of a Java stream pipeline and produces a final result or side effect.

A terminal operation finishes a Java Stream pipeline: it triggers evaluation and creates a final result. Until then, intermediate operations such as filter(), map(), and sorted() only describe work to perform. Common terminal operations include count(), toArray(), collect(), and toList(). section 9.2

Filtering requires a finishing operation

Calling filter() alone neither filters a list nor changes it. Keep the result of a terminal operation when a new list is wanted: stringList = stringList.stream() .filter(str -> !str.isEmpty()) .collect(Collectors.toList()); When mutation is intended and the list is mutable, removeIf(String::isEmpty) changes it in place instead. section 2.11

Treat intermediate functions as free of important side effects. Stream implementations may optimize pipeline stages, evaluate only the elements needed, or run work concurrently with parallelStream(). A side effect in filter(), map(), or peek() can therefore be skipped, reordered, repeated unexpectedly, or race with other work. Use collectors such as partitioningBy() when the real goal is to split elements into groups. section 9.2 section 9.13

Partition instead of mutating from filter

Rather than add rejected values to another list inside filter(), collect both groups explicitly: Map<Boolean, List<String>> partitions = list.stream() .collect(Collectors.partitioningBy(s -> s.contains("needle"))); This makes the final result, rather than an intermediate side effect, carry both partitions. section 9.2

A terminal operation also consumes a stream. Do not call count() and then anyMatch() on the same Stream, or retain one stream for repeated loop iterations; later use can fail with IllegalStateException. Reassign a stream when an intermediate operation returns a replacement pipeline, create a fresh stream from the source, or collect elements into an intermediate collection when multiple passes are necessary. section 9.3 section 9.13

stringList = stringList.stream().filter(str -> !str.isEmpty())
        .collect(Collectors.toList());
stringList = stringList.stream().sorted()
        .collect(Collectors.toList());

Compare ignored filtering and sorting pipelines with versions that finish via collect() and retain the resulting lists. section 2.11

How 100 Java Mistakes and How to Avoid Them covers it

How deep it goes
Defines pipeline execution and examines laziness, side-effect risks, and single-consumption failures without Stream implementation internals.
Examples
Uses filtering empty strings, partitioning strings by a substring, counting before anyMatch(), and reusing a stream in a loop.
What it assumes
Assumes familiarity with Java collections, lambda expressions, and fluent Stream calls while defining the pipeline’s terminal step.
What you can do after
Diagnose/debug with it by recognizing unevaluated pipelines and streams consumed before a later operation.
How it connects
Connects terminal operations with intermediate operations, collectors, side effects, parallel streams, and IllegalStateException.

Also explained in

Compare how 3 books explain terminal operation →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of 100 Java Mistakes and How to Avoid Them. The section links point to the parts of the book it draws on.