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