Unused Stream Pipeline
ID |
java.intermediate_stream_methods_left_unused |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
Java |
Tags |
reliability |
Description
Reports a statement whose whole effect is a Stream intermediate operation (map, filter, peek, sorted, distinct, limit, skip, flatMap) with nothing chained after it to run the pipeline.
Rationale
A Stream pipeline is lazy: none of its intermediate operations do any work until a terminal operation (forEach, collect, count, toList, …) is called. A statement that ends on an intermediate operation therefore has no effect at all — any lambda passed to it never executes:
List<String> names = ...;
names.stream().map(String::trim); // nothing happens: the pipeline is built and immediately discarded
This usually means a terminal operation was forgotten, or the developer expected map/peek to run the side effect eagerly the way a plain loop would.
Remediation
Add the terminal operation the pipeline was meant to end with:
List<String> names = ...;
List<String> trimmed = names.stream().map(String::trim).collect(java.util.stream.Collectors.toList());
What is not reported
Only the statement’s outermost call is checked: a pipeline followed by a terminal operation (forEach, collect, …), or whose result is assigned or returned rather than discarded, is not reported.
The receiver’s type must resolve to java.util.stream.. filter, map and peek are ordinary vocabulary well outside Stream — ByteBuffer.limit(int), InputStream.skip(long), a query builder’s filter(…) — so matching on the method name alone produced 261 findings on unrelated types across ten projects. someOptional.filter(…) left as a bare statement is therefore *not reported: the result is discarded there too, but Optional is not lazy, so the lambda does run and it is not the defect this rule names.
Demanding a resolved receiver costs two deliberate blind spots: a stream handed back by a project method the model cannot type, and a pipeline held in a var local. The chain parser types a receiver from its declaration, and var puts the type in the initializer, so var p = items.stream(); p.map(..); is silent while the same code written Stream<String> p = … is reported.