From what I can see, many of these supplier lambdas do return null idempotently, such as generate(queue::poll).
But with enough usage, I suspect we'll run into scenarios that may trip if called again after returning null. And if generate() is unordered, is it by-spec safe to depend on the elements being delivered in the order they are generated, at all? On Thu, Mar 5, 2026 at 1:33 AM Viktor Klang <[email protected]> wrote: > Is the supplier's get()-method allowed to be invoked *after* it has > previously returned *null?* > On 2026-03-05 06:54, Jige Yu wrote: > > Makes sense. > > What do you guys think of the idiom of > generate(supplierThatEventuallyReturnsNull) + takeWhile() ? Should it be > avoided? > > On Wed, Mar 4, 2026 at 6:59 AM Viktor Klang <[email protected]> > wrote: > >> >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. >> >> In this specific case I'd argue that it's more correct (and more >> performant, and less code) to just use the 3-arg iterate. >> >> >or should I reconsider my warnings about side effects being rearranged >> in sequential streams? >> >> Personally I prefer my Streams correct regardless of underlying >> implementation and regardless of whether the stream isParallel() or not. >> On 2026-03-03 20:29, Jige Yu wrote: >> >> 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 >>> record E(E e) {} >>> Stream.iterate(new E(new E(new E(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(new E(new E(new E(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 >>> Exception java.lang.NullPointerException: Cannot invoke >>> "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(new E(new E(new E(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(new E(new E(new E(null))), e -> e.e())< /span> >>> .parallel() >>> .distinct() >>> .takeWhile(Objects::nonNull) >>> .forEach(IO::println) >>> ``` >>> ```java >>> Exception java.lang.NullPointerException: Cannot invoke >>> "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 >>> >>> -- >> Cheers, >> √ >> >> >> Viktor Klang >> Software Architect, Java Platform Group >> Oracle >> >> -- > Cheers, > √ > > > Viktor Klang > Software Architect, Java Platform Group > Oracle > >
