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())
.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())
.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())
.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())
.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