Hi Jige,

Yes, if deterministic behavior is desired, then using that combination would be non-deterministic. See: https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/stream/Stream.html#takeWhile(java.util.function.Predicate)

On 2026-03-09 18:20, Jige Yu wrote:
Hi Viktor,

Thanks, that is useful to know.

To ensure I understand your recommendation correctly, would you discourage the use of /Stream.generate(queue::poll).takeWhile(Objects::nonNull)/ because of its unordered nature?

Best,

Jige Yu

On Mon, Mar 9, 2026 at 3:41 AM Viktor Klang <[email protected]> wrote:

    I personally place emphasis to write code that is correct
    regardless of whether the stream is sequential or parallel, so in
    that case, using an unordered generator where an ordered generator
    is required for correctness would be considered, by me, as a bug.

    On 2026-03-08 02:43, Jige Yu wrote:
    Viktor, can you be more specific about how you see this statement
    apply to the question of generate()'s unordered-ness?

    On Fri, Mar 6, 2026 at 1:07 AM Viktor Klang
    <[email protected]> wrote:

        From that documentation:

        /«For sequential streams, the presence or absence of an
        encounter order does not affect performance, only
        determinism. If a stream is ordered, repeated execution of
        identical stream pipelines on an identical source will
        produce an identical result; if it is not ordered, repeated
        execution might produce different results.»/

        On 2026-03-06 04:00, Jige Yu wrote:


        On Thu, Mar 5, 2026 at 3:13 PM Viktor Klang
        <[email protected]> wrote:

            >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


        Yeah. Specifically this statement:

        > if the source of a stream is a |List| containing |[1, 2,
        3]|, then the result of executing |map(x -> x*2)| must be
        |[2, 4, 6]|. However, if the source has no defined encounter
        order, then any permutation of the values |[2, 4, 6]| would
        be a valid result.
        generate()'s source isn't a List, and it's specified as "an
        infinite sequential unordered stream", which to me reads as
        "it has no defined encounter order".

        And if that reading is correct, then any permutation of the
        generated results would be a valid result?

            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

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