>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?

Encounter order for unordered streams is described here: https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/stream/package-summary.html#Ordering

On 2026-03-06 00:04, Jige Yu wrote:
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
            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

-- 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

Reply via email to