peek?
Why does the documentation recommend using Stream.peek mainly for debugging? Let’s find out.
Stream API
Since peek is part of the Stream API, let’s start by looking at the Stream API documentation.
According to the Stream API documentation, functions passed to methods like filter
and map
must satisfy two constraints for correct operation:
- They must be non-interfering
- In most cases, they should be stateless
Let’s focus on non-interfering.
non-interfering
Dictionary meaning of interfering
Interfering, meddlesome
What non-interfering means in the documentation
It means that the data source must not be modified at all during the execution of the stream pipeline. - Reference
If you violate this:
- Unintended results may occur
- Exceptions may be thrown
Based on this, let’s look at how peek is described in the Java docs.
Peek
Here are two things to note:
- The action parameter passed to peek must be a non-interfering action
- peek is an intermediate operation
In other words, you must not modify the source data in a peek operation.
Why is being an intermediate operation important? Let’s look at some examples.
Examples
Let’s see if the source data is actually modified and what unintended results peek can cause.
Modifying the source data
Code
|
|
Result

the value of the elements in the origin list (the data source) was changed.
peek and count - 1
Code
What will the following code print to the console?
|
|
Result

There is no output. In Java 9, due to optimization of the terminal operation count, the function passed to peek is not executed.
peek does not affect the count, so the function for standard output is not executed.
In other words, intermediate operations may not be executed for optimization (see lazy evaluation).
peek and count - 2
Code
|
|
Result

Since filter affects the result of count, peek is evaluated in this case.
Caution
Here are some points to keep in mind when using peek in development:
- peek is an intermediate operation, so it may not be evaluated depending on the terminal operation (due to lazy evaluation and optimization)
- Using peek differently from the documentation may cause problems in future versions (e.g., count() optimization from Java 8 to Java 9)
Finally, let’s look at methods with different paradigms and wrap up.
Stream.forEach vs Iterable.forEach
Although they have the same method name, Stream.forEach is made for the functional paradigm, while Iterable.forEach is for the procedural paradigm. Let’s see how their documentation differs.
Stream.forEach
also specifies that you should pass a non-interfering action - link
Iterable.forEach
only warns that modifying the source element may cause side effects - link
Thoughts - Expression
peek and forEach can both cause side-effects.
- Even standard output is defined as a side effect. Among side effects, you should implement non-interfering actions that do not modify the data source.