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

Reply via email to