An intermediate operation transforms or selects elements in a Java Stream pipeline and returns another stream. Typical operations include map(), filter(), skip(), limit(), and peek(). They build a deferred recipe: no stream processing starts until a terminal operation such as toList(), count(), collect(), or anyMatch() runs. section 9.2
Treat functions supplied to intermediate operations as free of important side effects. Stream processing may skip work, vary evaluation order, or run concurrently with parallelStream(); a lambda that mutates external state can therefore produce missing, reordered, duplicated, or unsafe results. peek() does not make mutation reliable. section 9.13 section 9.2
List<String> containNoNeedle = new ArrayList<>();
List<String> containNeedle = list.stream()
.filter(str -> {
if (str.contains("needle")) {
return true;
}
containNoNeedle.add(str);
return false;
})
.toList();
This filter both selects strings and appends rejected strings elsewhere. Its result now depends on how often and in what order the predicate runs. section 9.2
Map<Boolean, List<String>> partitions = list.stream()
.collect(Collectors.partitioningBy(str -> str.contains("needle")));
List<String> containNoNeedle = partitions.get(false);
List<String> containNeedle = partitions.get(true);
When both matching and nonmatching elements are needed, partitioningBy() expresses the two outputs without mutating a second collection during filtering. section 9.2
Intermediate operations also create a stream-ownership trap. Usually, an operation returns a new stream while marking the prior stream as consumed. If a stream variable is conditionally transformed but the returned stream is ignored, later terminal processing can fail with IllegalStateException. Reassign the variable, or encode the condition directly in the pipeline. section 9.3
Stream<String> stream = strings.stream();
if (skipHeader) {
stream = stream.skip(1); #1
}
List<String> trimmed = stream.map(String::trim).toList();
Reassigning stream after skip(1) preserves the pipeline that the terminal operation must consume. section 9.3
Use intermediate operations safely:
- End the pipeline with one terminal operation; a stream cannot be consumed twice. section 9.13 section 9.3
- Capture returned streams when adding conditional operations such as skip(). section 9.3
- Collect to an intermediate collection or use a loop when later logic needs to traverse the same results again. section 9.3
- Prefer a collector such as partitioningBy() when the apparent need for side effects is really a need for multiple result groups. section 9.2
Conditional skipping
Incorrect code calls stream.skip(1) and then continues with the original variable. Use stream = stream.skip(1), or write skip(skipHeader ? 1 : 0), before map(String::trim).toList(). The latter keeps the conditional behavior in one consumable pipeline. section 9.3


