Reused Consumed Stream
ID |
java.consumed_stream_pipelines_reused |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
Java |
Tags |
reliability |
Description
Reports a Stream-typed local variable that is used again, later in the same method, after a terminal operation (forEach, collect, count, …) has already run through it.
The two usages must be able to run on one and the same path. A consumption that sits under a branch the later usage is outside of — the two arms of an if or a switch, a returning if and the code that follows it, a loop or lambda body — is not reported: one of the two never runs, so nothing is traversed twice. The test is one-sided, so a usage that always runs followed by one inside a branch is still reported; it throws whenever that branch is taken.
A consumption inside a loop body or an if condition followed by a later use is a known false negative of that test.
Rationale
A JDK Stream can be traversed only once. Once a terminal operation has consumed it, any further operation on the same reference — terminal or not — throws IllegalStateException at runtime:
Stream<String> pipeline = names.stream();
pipeline.forEach(System.out::println);
long total = pipeline.count(); // throws IllegalStateException: stream has already been operated upon or closed
Remediation
Build the pipeline again from the source collection for the second traversal, or restructure the code so a single pipeline produces everything that is needed:
Stream<String> pipeline = names.stream();
pipeline.forEach(System.out::println);
long total = names.stream().count();
What is not reported
Only a local declared with the exact type Stream is tracked — a var-declared local, and the primitive stream types (IntStream, LongStream, DoubleStream), are out of scope. Usages are only followed within the declaring method: a field or a variable captured by a lambda and used elsewhere is not tracked. The check has no control-flow awareness — a reassignment (pipeline = …;) is treated as a fresh value and clears the "already consumed" state, but two usages in mutually exclusive if/else branches are still seen as sequential.