Hi Viktor,
Thanks for the explanation!
I also experimented with adding parallel() in the
middle, and it indeed threw a NullPointerException even
without distinct().
In our codebase, I see some developers using iterate()
+ takeWhile() and others using generate() +
takeWhile(). I am debating whether to raise a concern
about this pattern. Most likely, people won't insert
intermediary operations between them, and I worry I
might be overthinking it.
However,
generate(supplierThatMayReturnNull).takeWhile() seems
even more precarious. Since generate() is documented as
unordered, could it potentially return elements out of
encounter order, such as swapping a later null with an
earlier non-null return?
This brings me back to the rationale I’ve used to
discourage side effects in map() and filter(). In a
sequential stream, I’ve argued that relying on side
effects from an earlier map() to be visible in a
subsequent map() is unsafe because the stream is
theoretically free to process multiple elements through
the first map() before starting the second.
Is that view too pedantic? If we can safely assume
iterate() + takeWhile() is stable in non-parallel
streams, should the same logic apply to subsequent
map() calls with side effects (style issues aside)?
I’m trying to find a consistent theory. Should I advise
my colleagues that iterate() + takeWhile() and
generate() + takeWhile() are unsafe, or should I
reconsider my warnings about side effects being
rearranged in sequential streams?
I hope that clarifies the root of my confusion.
Best,
Jige Yu
On Mon, Mar 2, 2026 at 6:08 AM Viktor Klang
<[email protected]> wrote:
Hi Jige,
I think I understand what you mean. In this case
you're trying to prevent a `null`-return from
`nextOrNull()` to be fed into the next iteration
and thus throwing a NullPointerException.
Now the answer is going to be a bit nuanced than
you might want to hear, but in the spirit of
providing clarity, the code which you provided will
"work" under the assumption that there is no
"buffer" in between iterate(…) and takeWhile(…).
TL;DR: use Stream.iterate(seed, e -> e != null, e
-> e.nextOrNull())
Long version:
Imagine we have the following:
```java
recordE(Ee) {}
Stream.iterate(newE(newE(newE(null))), e ->e.e())
.< /span>takeWhile(Objects::nonNull)
.forEach(IO::println)
```
We get:
```java
E[e=E[e=E[e=null]]]
E[e=E[e=null]]
E[e=null]
```
However, if we do:
```java
Stream.iterate(newE(newE(newE(null))), e ->e.e())<
/span>
.gather(
Gatherer.<E,ArrayList<E>,E>ofSequential(
ArrayList::new,
(l, e, _) ->l.add(e),
(l, d) ->l.forEach(d::push)
)
)
.takeWhile(Objects::nonNull)
.forEach(IO::println)
```
We get:
```java
Exceptionjava.lang.NullPointerException:Cannotinvoke
"REPL.$JShell$16$E.e()"because "<parameter1>"is null
at lambda$do_it$$0(#5:1)
at Stream$1.tryAdvance(Stream.java:1515)
at
ReferencePipeline.forEachWithCancel(ReferencePipeline.java:147)
at
AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:588)
at AbstractPipeline.copyInto(AbstractPipeline.java:574)
at
AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:560)
at
ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:153)
at
ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:176)
at AbstractPipeline.evaluate(AbstractPipeline.java:265)
at
ReferencePipeline.forEach(ReferencePipeline.java:632)
at(#5:9)
```
But if we introduce something like `distinct()`in
between, it will "work" under sequential processing,
but under parallel processing it might not, as the
distinct operation will have to buffer
*separately*from takeWhile:
```java
Stream.iterate(newE(newE(newE(null))), e ->e.e())<
/span>
.distinct()
.takeWhile(Objects::nonNull)
.forEach(IO::println)
```
```java
E[e=E[e=E[e=null]]]
E[e=E[e=null]]
E[e=null]
```
Parallel:
```java
Stream.iterate(newE(newE(newE(null))), e ->e.e())<
/span>
.parallel()
.distinct()
.takeWhile(Objects::nonNull)
.forEach(IO::println)
```
```java
Exceptionjava.lang.NullPointerException:Cannotinvoke
"REPL.$JShell$16$E.e()"because "<parameter1>"is null
at lambda$do_it$$0(#7:1)
at Stream$1.tryAdvance(Stream.java:1515)
at
Spliterators$AbstractSpliterator.trySplit(Spliterators.java:1447)
at AbstractTask.compute(AbstractTask.java:308)
at CountedCompleter.exec(CountedCompleter.java:759)
at ForkJoinTask.doExec(ForkJoinTask.java:511)
at ForkJoinTask.invoke(ForkJoinTask.java:683)
at
ReduceOps$ReduceOp.evaluateParallel(ReduceOps.java:927)
at DistinctOps$1.reduce(DistinctOps.java:64)
at
DistinctOps$1.opEvaluateParallelLazy(DistinctOps.java:110)
at
AbstractPipeline.sourceSpliterator(AbstractPipeline.java:495)
at AbstractPipeline.evaluate(AbstractPipeline.java:264)
at
ReferencePipeline.forEach(ReferencePipeline.java:632)
at(#7:4)
```
On 2026-03-01 06:29, Jige Yu wrote:
Hi @core-libs-dev,
I am looking to validate the following idiom:
Stream.iterate(seed, e -> e.nextOrNull())
.takeWhile(Objects::nonNull);
The intent is for the stream to call nextOrNull()
repeatedly until it returns null. However, I am
concerned about where the Stream specification
guarantees the correctness of this approach
regarding happens-before relationships.
The iterate() Javadoc defines happens-before for
the function passed to it, stating that the action
of applying f for one element happens-before the
action of applying it for subsequent elements.
However, it seems silent on the happens-before
relationship with downstream operations like
takeWhile().
My concern stems from the general discouragement
of side effects in stream operations. For example,
relying on side effects between subsequent map()
calls is considered brittle because a stream might
invoke the first map() on multiple elements before
the second map() processes the first element.
If this theory holds, is there anything
theoretically preventing iterate() from generating
multiple elements before takeWhile() evaluates the
first one? I may be overthinking this, but I would
appreciate your insights into why side effects are
discouraged even in ordered, sequential streams
and whether this specific idiom is safe.
Appreciate your help!
Best regards,
Jige Yu
--
Cheers,
√
Viktor Klang
Software Architect, Java Platform Group
Oracle