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