Skip to content

Typos and possible small errors in the Streams articles ( https://dev.java/learn/api/streams/ ) #200

Open

Description

@willy-b

Hello dev.java team!
Thanks for the excellent https://dev.java/learn/api/streams/ series of articles on Java Streams.

I would like to report a few typos and other small possible mistakes.


In the latest version of article "Adding a Terminal Operation on a Stream", https://dev.java/learn/api/streams/terminal-operations/ (archived at https://web.archive.org/web/20260225004953/https://dev.java/learn/api/streams/terminal-operations/ ):

  • A verbatim duplicated code block is introduced with the expectation of being distinct.
    After the code block:
Stream<String> strings = Stream.of("one", "two", "three", "four");

List<String> result =
    strings.filter(s -> s.length() == 3)
           .map(String::toUpperCase)
           .toList();
IO.println("result = " + result);

is presented,
it is said "Starting with Java SE 16, you have a second, even simpler, pattern."
followed by a code block with:

Stream<String> strings = Stream.of("one", "two", "three", "four");

List<String> result =
    strings.filter(s -> s.length() == 3)
           .map(String::toUpperCase)
           .toList();
IO.println("result = " + result);

Which is identical, and thus cannot be "even simpler", suggesting this was repeated in error, see screenshot below:

Image

Maybe the first block was supposed to use ".collect(Collectors.toList())" and the 2nd block ".toList()" (which is newer and simpler)?

However, the article also says the first pattern produces a modifiable list in contrast to the 2nd, but in fact .collect(Collectors.toList()), while not guaranteed to produce an unmodifiable list (unlike .toList),
has no guarantee on modifiability at all, with the documentation stating "there are no guarantees on the type, mutability, serializability, or thread-safety of the List returned; if more control over the returned List is required, use toCollection(Supplier)." (https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/stream/Collectors.html#toList() ).
So the sentence "If what you need is a modifiable list, you should stick to the first collector pattern" must be referring to something else, like .forEach(someList::add) for a manually created mutable list, someList, or should refer to Collectors.toCollection .

  • In the next section, when introducing the ".collect(Collectors.toList())" approach, the text states "You already used this pattern in a previous example", when no previous example in this article used this approach. (This also suggests that in the duplicated block above, maybe one of the blocks was intended to be using ".collect(Collectors.toList())" instead of ".toList()"? I think .toList() came in SE 16, so maybe that was supposed to be the second example and ".collect(Collectors.toList())" the first?)

  • Not a mistake, but repetition worth reviewing for possible consolidation:
    "Starting with Java SE 16, there is a better way to collect your data in an immutable list, which can be more efficient on some cases. [Same code block as presented 2x above, showing Stream.toList() again]"
    "This optimization has been implemented in the Stream.toList() method, which has been added to Java SE 16. If what you need is an immutable list, then you should be using this pattern."

  • Re: "The supplier you used to tune the initial size of you instance of ArrayList can also be used to build any implementation of Collection, including implementations that are not part of the JDK."
    In "used to tune the initial size of you instance", "you instance" should be "your instance".

  • Re: "We already covered the max() and min() methods from the specialized streams of numbers: IntStream, LongStream and DoubleStream. You know that these operations do not have an identity element, so you should not be surprised to discover that there are all returning optional objects."
    "there are all returning optional objects" should be "they are all returning optional objects"

  • In "You may have noticed an important difference between the different terminal operation that we have covered here.", "operation" should be plural.


In the latest version of the "Creating Streams" article, https://dev.java/learn/api/streams/creating/ (as archived at https://web.archive.org/web/20260224131949/https://dev.java/learn/api/streams/creating/ ):

  • In the sentence "The random numbers depends on that seed", "depends" should be "depend".

  • Potentially confusing: random.ints() described with a range which might not be recognized as matching the code
    (endpoints excluded range; English typically uses endpoints inclusive; and the APIs, including Random.ints(), half-open / half-closed intervals, specifically right-open aka start-inclusive-end-exclusive)

Specifically, it is written "Here is a first pattern of code that generates 10 random integers between 0 and 5."
when the associated code snippet uses

random.ints(10, 1, 5)
          .boxed()
          .toList();

which would return random integers between 1 (inclusive) and 5 (exclusive) (the right-open interval [1, 5)), which is between 0 and 5 only if both endpoints are treated as exclusive (open integer interval (0, 5)) (the text does not specify that both endpoints are excluded).

Endpoints excluded integer intervals/ranges are unusual in both plain English (typically inclusive) and programming (typically half-open / half-closed).

E.g. the Java language specification v25 in English gives numeric ranges typically inclusive, like "an integer between [Emin] and [Emax], inclusive" or "the components of the array are referenced using integer indices from 0 to n - 1, inclusive" or "the shift distance actually used is therefore always in the range 0 to 31, inclusive", etc, while the Java APIs usually use half-open / half-closed intervals (which combine well without double counting), e.g. as in the API used here Random.ints() but also String.substring(), Time APIs like Duration.between, or many other places.

(Many applications use half-open intervals (typically right-open inclusive-exclusive) as any interval can be subdivided easily into half-open intervals without overlap or double counting; intervals excluding both endpoints do not combine well and are usually not used; that is e.g. the right-open interval [1, 3) can be combined with [3, 5) to intuitively get the new half-open interval spanning both, [1, 5). Whereas combining open intervals (both endpoints excluded) with the same endpoints does not even result in an interval, e.g. (1,3) combined with (3, 5) gives the disconnected integers 2 and 4 ({2, 4})).

  • "Being able to process these lines with the Stream API gives you a more readable and more maintainable code."
    "gives you a more readable and more maintainable code" should be "gives you more readable and maintainable code"

  • "To run it, you need to copy it, paste it in your IDE, and adjust de path to fit your local installation."
    "adjust de path" should be "adjust the path"

  • "Note that you cannot run this code in your brower." "brower" should be "browser".


In the latest version of an article "Reducing a Stream", https://dev.java/learn/api/streams/reducing/ (archived at https://web.archive.org/web/20260224224658/https://dev.java/learn/api/streams/reducing/ )

  • In the sentence "There are two caveats you need to understand though. Let us the first one here and the second one in the next section. ",
    "Let us the first one here" is missing a word after "us". Maybe it was intended to read "Let us discuss the first one here" or something similar.

  • Re: "A way to check for that property can be just to run your binary operator on random data and verify if you always get the same result. If you do not, then you know your binary operator is not associative. If you do, then, unfortunately, you cannot conclude reliably." Not a mistake but the last sentence seems unclear. It might be clearer if the last sentence were replaced with something like "If you do, then the operator might be associative but it still may not be, as such a result is inconclusive."

  • Re: "Let us an example with a reduction that does not have an identity element."
    "Let us an" should be "Let us do an".

  • Re: "The combiner combines two integers: the partial sums of the lengths of the strings processed so far. So the identity element you need to provide is the identity element: O."
    "O" should be the number "0".

  • In the sentence: "This way of writing thing clearly shows the fusion of the mapping, modeled by the mapper and the reduction, modeled by the binary operator":
    "thing" should be "things", and I think there should be a comma after "mapper".


In the latest version of the article "Adding Intermediate Operations on a Stream", https://dev.java/learn/api/streams/intermediate-operation/ (archived at https://web.archive.org/web/20260224062952/https://dev.java/learn/api/streams/intermediate-operation/ ),

  • In the sentence, "This mapToInt() method takes a ToIntFuction as an argument." , "ToIntFuction" should be "ToIntFunction"

  • Under "Filtering a Stream",
    "This method is available on streams of objects and stream of primitive types." , it should read "and streams of primitive types" with a plural streams for both types of streams.

In the next example's code block it is written:

Country uk = new Country("United Kindgom", List.of(london, manchester));

with a typo in "United Kindgom".

  • Re: "The reason why it is better to use the flatMap() way is that concat() creates intermediates streams during the concatenation." This should use "intermediate" instead of "intermediates".

Thanks so much for all this content!

(If these typo/error reports continue to be desired, it is fine to leave this issue open even after fixing what is reported above so I can add any additional items found, as I am still working through the Streams articles.)

Activity

  1. JosePaumard commented on Feb 25, 2026

    @JosePaumard
    Contributor

    Thank you Willy for your report! I just pushed a fix.
    As for the collect(Collectors.toList()). It is not stated that it returns an ArrayList. But it does, and changing this implementation would be a breaking change. It is now set in stone, it will never change. Especially to a list that would be non-modifiable.
    And yes, you can totally keep sending reports, many thanks for them!

  2. willy-b commented on Feb 26, 2026

    @willy-b
    Author

    Great! Here's another batch of little issues found in these Streams articles.

    In the "Using Collectors" article at https://dev.java/learn/api/streams/using-collectors (archived at https://web.archive.org/web/20260225070958/https://dev.java/learn/api/streams/using-collectors/ ),

    • "This second example creates a HashSet with only even numbers and without duplicates"
      produces a result that does not match that description (includes non-evens).
    Image

    The following code snippet is shown:

    Set<Integer> evenNumbers =
       IntStream.range(0, 10)
                .map(number -> number / 2)
                .boxed()
                .collect(Collectors.toSet());
    IO.println("evenNumbers = " + evenNumbers);
    

    with output:

    
    evenNumbers = [0, 1, 2, 3, 4]
    
    

    Which are not all even numbers, e.g. 1 and 3 are included.

    The result is without duplicates,
    but to give even numbers, it would need to be updated, one way would be to have .filter(number -> number % 2 == 0) instead of .map(number -> number / 2).

    An updated version of the code could be:

    Set<Integer> evenNumbers =
       IntStream.range(0, 10)
                .filter(number -> number % 2 == 0)
                .boxed()
                .collect(Collectors.toSet());
    IO.println("evenNumbers = " + evenNumbers);
    

    e.g.:

    jshell> Set<Integer> evenNumbers =
      ...>    IntStream.range(0, 10)
      ...>             .filter(number -> number % 2 == 0)
      ...>             .boxed()
      ...>             .collect(Collectors.toSet());
    evenNumbers ==> [0, 2, 4, 6, 8]
    
    • In the sentence, "The first one we cover creates map with boolean keys. It is created with the partitionningBy() factory method. "
      "creates map" should be "creates a map", and "partitionningBy()" should be "partitioningBy()".

    • In the sentence, "The collector you can use to create histogram is created with the Collectors.groupingBy() method", "to create histogram" should be "to create histograms".

    • In the sentence, "The groupingBy() collector supports this, and gather all these elements in a list, bound to that key. ", "and gather all" should be "and gathers all"

    • In the sentence, "Let examine the following example.", "Let examine" should be "Let us examine"

    • In the sentence, "(element1, element2) -> element1 + ", " + element2) is the merge function, called with the two elements that have generated the same key."
      there is an extra ) at the end of the lambda

    • In the sentence, "The Collectors.mappping() factory method creates a regular collector." , "Collectors.mappping()" should be "Collectors.mapping()"


    In the article on "Finding the Characteristics of a Stream", https://dev.java/learn/api/streams/characteristics/ (archived at: https://web.archive.org/web/20260225071047/https://dev.java/learn/api/streams/characteristics/ ),

    • In the sentence, "Covering this spliterator object in details is beyond the scope of this tutorial", "in details" should be "in detail".

    • In the sentence, "Let us present each characteristic in more details", it seems "details" should be "detail."

    • In the sentence "To check is a stream has a given characteristics, you need to check for a given bit in a word within the spliterator of a stream"
      "To check is a stream has a given characteristics" should probably read "To check IF a stream has given characteristics",
      and "check for a given bit in a word within the spliterator of a stream" might be replaced with "call the hasCharacteristics() method of the spliterator of the stream with the characteristic values to check or directly check for a given bit within the bitmap returned by the .characteristics() method"

    • In the sentence "Keeping track of the order of the elements of a stream may lead to overheads for parallel streams", "overheads" should be "overhead."

    • In the sentence "strings.stream() is not DISTINCT as it is build from an instance of List.", "as it is build from" should be "as it is built from."

    • In the text, "the parallelization mechanism splits a stream in two parts and distribute the computation", (1) "distribute" should be "distributes" and (2) it might be an improvement to write "splits a stream INTO two parts" instead of "in two parts", but (2) is a matter of taste.

    • In the sentence, "It is always better to have SIZED and SUBSIZED stream for parallel computations.", "stream" should be "streams".

    Thanks!

  3. JosePaumard commented on Feb 26, 2026

    @JosePaumard
    Contributor

    Thank you again for your report! I just pushed a fix.
    I rephrased the part on the characteristic, I hope it's more clear as it is.

  4. willy-b commented on Feb 26, 2026

    @willy-b
    Author

    Thanks! (and N.B. I do reply re: the spliterator change in the 2nd block below.)


    Note, FYI, you have still not yet fixed or replied re: the "evenNumbers" example in the "Using Collectors" article at https://dev.java/learn/api/streams/using-collectors (archived: https://web.archive.org/web/20260226143455/https://dev.java/learn/api/streams/using-collectors/ ) reported in the last comment.

    • "This second example creates a HashSet with only even numbers and without duplicates"
      produces a result that does not match that description (includes non-even numbers, e.g. 1 and 3).

    See screenshot, details, and a suggested fix in my comment above at #200 (comment) . (Note that 0 is fine to include in the even numbers, per e.g. https://oeis.org/A005843 or https://web.archive.org/web/20260226161619/https://mathworld.wolfram.com/EvenNumber.html , but not 1 and 3 as the team's article's code also outputs in that example).

    • Also, I can add one I left out of the previous report as it is merely unclear, not strictly incorrect but may be of interest to update: The section Extracting Maxes from a Histogram->Extracting a Non-Ambiguous Max

    introduces an example of computing the maximum key-value pair of the map produced by groupingBy on a stream,
    and solves it two ways:
    (1) using a generic Map.Entry comparator (.comparingByValue()) with the Stream.max operation
    (2) using a custom record with a custom comparator method (record class NumberOfLength with comparingByLength())

    It is not clear whether it is intentional that when comparing the two methods to find the "Non-Ambiguous Max", approach (1) takes the maximum comparing values of the key-value pairs (in this case the number of strings with a certain length), and approach (2) switches to comparing keys (string lengths observed, for which counts were accumulated).

    This seems unintentional, like if the author's mind changed and they did not update consistently, since the problem is introduced clearly as computing the maximum using the value of the key value pair (finding string length which was most frequent), e.g.:

    
    Printing this histogram gives you the following result.
    
    3 :: 4
    4 :: 3
    5 :: 3
    6 :: 2
    
    Extracting the maximum value from this histogram should give you the result: 3 :: 4 [four strings had length three].
    
    

    The solution using approach (1) gives "3 :: 4" , but then for (2) the criterion is switched, not just the methodology, so that the result "6 :: 2" is produced instead.
    Additional evidence it might be unintentional that the criterion switched is in the next sentence after the conclusion of that example, which says "The previous example was a nice example, because there was only one maximum value in your list. "


    Yes, re: the spliterator, in the sentence that originally read

    "To check is a stream has a given characteristics, you need to check for a given bit in a word within the spliterator of a stream"

    where I had commented above:

    "To check is a stream has a given characteristics" should probably read "To check IF a stream has given characteristics",
    and "check for a given bit in a word within the spliterator of a stream" might be replaced with "call the hasCharacteristics() method of the spliterator of the stream with the characteristic values to check or directly check for a given bit within the bitmap returned by the .characteristics() method"

    You have updated it to

    "Each characteristic is stored in a specific bit of a word carried by the spliterator. This word is returned by the Spliterator.characteristics() method. So to check if a stream has a given characteristic, you need to check if the corresponding bit is a 0 or a 1."

    This is fine by me! I still think it may be worth mentioning the hasCharacteristics(int) method which can be used with e.g. Spliterator.DISTINCT, Spliterator.ORDERED, or some combination of these, etc, but I am fine with how it is now.
    Referring to the int as a word is obviously ok (e.g. this terminology is even used in the JVM spec v25 ( https://docs.oracle.com/javase/specs/jvms/se25/jvms25.pdf ) section 4.10.1.2 which explains how the type "oneWord" is the supertype of int, float, and reference and it is clear from this context that out of those 3 it must be int ).


    Re: https://dev.java/learn/api/streams/terminal-operations/ ,

    Thanks for the updates to multiple sections, but especially the "Consuming Each Element One by One" section of https://dev.java/learn/api/streams/terminal-operations/ (archived: https://web.archive.org/web/20260226200825/https://dev.java/learn/api/streams/terminal-operations/#foreach ) , as the repeated block I reported earlier ( #200 (comment) ) has been replaced, and it has been re-written in a very clear way generally, comparing and contrasting .collect(Collectors.toList()) and .toList() instead of repeating two .toList() examples back to back unintentionally (compare with previous version https://web.archive.org/web/20260225004953/https://dev.java/learn/api/streams/terminal-operations/#foreach ).


    Another two items not yet fixed or replied to though reported above in #200 (comment):


    Then, in the latest version of "Creating Your Own Collector", https://dev.java/learn/api/streams/custom-collectors/ (archived at
    https://web.archive.org/web/20260225071030/https://dev.java/learn/api/streams/custom-collectors/ ):

    • Re: "In a first step, you built an histogram of type Map<Inter, Long>,", "Map<Inter, Long>" should probably be "Map<Integer,Long>".

    • Re: "Let us go through a use case to see what you can do with collector." "collector" should be "collectors".

    • In the first code block introducing the Vehicle interface and Car and Truck records, it is described in the text that Car and Truck implement Vehicle
      but the "implements Vehicle" is apparently accidentally omitted from the record declarations (in the subsequent example code block that uses Car and Truck, it is fixed)

    Image
    • Re: "It is still possible create this union on one pass your collection of vehicles, but the predicate you will be using to filter your data is becoming more and more complex."
      "It is still possible create this union" should be "It is still possible TO create this union";
      "on one pass your collection" should be "on one pass of your collection"

    Thanks very much!

  5. JosePaumard commented on Feb 27, 2026

    @JosePaumard
    Contributor

    Thank you again for your report! I just pushed a fix.

    • I fixed the maxes example, by changing the record. This is where the mistake was made.
    • I added an example for the Spliterator.hasCharacteristics() method.
      I am a little worried, because some of the fixes that I did apparently didn't make it to the live site. So I pushed them again, I hope it will work this time.
  6. willy-b commented on Mar 19, 2026

    @willy-b
    Author

    Thanks so much!

    I can confirm I can now see the added example showing alternatively how to use the Spliterator.hasCharacteristics method as discussed above ( https://web.archive.org/web/20260318235843/https://dev.java/learn/api/streams/characteristics/ ) and that the "Extracting a Non-Ambiguous Max" example has been fixed as of https://web.archive.org/web/20260318231310/https://dev.java/learn/api/streams/using-collectors/ .

    But, indeed it does seem some of your intended updates are not yet showing on the live site (see below).


    For https://dev.java/learn/api/streams/using-collectors/ ,
    The source code was updated but not the expected output for the example previously on "evenNumbers" (it now has a different intended output vs https://web.archive.org/web/20260226143455/https://dev.java/learn/api/streams/using-collectors/ ).

    E.g. as archived today https://web.archive.org/web/20260318231310/https://dev.java/learn/api/streams/using-collectors/ ,
    it still shows the code snippet output as:

    Running this code gives you the following result.
    evenNumbers = [0, 1, 2, 3, 4]

    Image

    Also, re: the "Combining the Results of Two Collectors with the Teeing Collector" section of https://dev.java/learn/api/streams/custom-collectors/ (archived as is in https://web.archive.org/web/20260318235113/https://dev.java/learn/api/streams/custom-collectors/ ), which does not show in the sidebar as a section despite being a H2 like other sections, as reported earlier
    the implements Vehicle (Vehicle being an empty interface) has not been added to the first Car/Truck examples yet on teeing only the second (appears accidental).

    Then, presumably the Vehicle interface is empty of methods intentionally to contrive an excuse for using teeing, but it may be observed that since both record classes include the record component "engine" of the same type (and also "drive"),
    the teeing example could be updated to include the accessor methods for those common components for the Car and Truck in the Vehicle interface:

    interface Vehicle {
       Engine engine();
       Drive drive();
    }
    

    then the teeing code could be simplified from using instanceof checks for Car or Truck to call the common method "engine()":
    (existing version)

    List<Vehicle> electricVehicles = vehicles.stream()
            .collect(
                    Collectors.teeing(
                            Collectors.filtering(
                                    vehicle -> vehicle instanceof Car car && car.engine() == Engine.ELECTRIC,
                                    Collectors.toList()),
                            Collectors.filtering(
                                    vehicle -> vehicle instanceof Truck truck && truck.engine() == Engine.ELECTRIC,
                                    Collectors.toList()),
                            (cars, trucks) -> {
                                cars.addAll(trucks);
                                return cars;
                            }));
    

    to call this method on the Vehicle interface, which obviates using teeing at all, allowing use of plain .filter():

    List<Vehicle> electricVehicles = vehicles.stream().filter(vehicle -> vehicle.engine() == Engine.ELECTRIC)
            .collect(Collectors.toList());
    
    Image

    It might be didactic to also show that approach to highlight how teeing might not always be the best solution.


    Thanks!

  7. JosePaumard commented on Mar 20, 2026

    @JosePaumard
    Contributor

    Thank you again for yout report! I just pushed a fix.
    As for the teeing() Collector: I updated the example to use a sealed interface. The idea behind that it to stay within the Data Oriented Programming ideas, and avoiding adding any behavior on the sealed types. If you add a vehicle later that does not have the behavior you put in the interface, then the refactoring will be much harder.
    Now I know that this example is not great and needs some attention. There are limitations in the Playground (one cannot use java.io for instance) that makes the crafting of these examples trickier.

  8. willy-b commented on Apr 14, 2026

    @willy-b
    Author

    Great! Thanks for the updates!

    And thanks for your patience, since my last comment here I have been working on #210 on the File Systems tutorial, #206 on the Collections Framework tutorial, #218 on the Garbage Collection tutorial, #211 on the Classes and Objects tutorial, #215 on the Switch tutorial (and others) in parallel.


    Re: the "Combining the Results of Two Collectors with the Teeing Collector" section of https://dev.java/learn/api/streams/custom-collectors/ ,
    and your choice using teeing() without noting that one could instead add an abstract engine() method to the interface (Records can override interface methods with their accessors) to simplify, I just wanted in my last comment to show the stream filtering in that case could be done in one line (given engine() added to Vehicle interface: vehicles.stream().filter(vehicle -> vehicle.engine() == Engine.ELECTRIC).collect(Collectors.toList());) on the off chance you were not aware, now that it is confirmed to be an intentional choice (to avoid committing to all vehicles having engines at all or needing to return null if they don't for example), no worries.


    Re: https://dev.java/learn/api/streams/using-collectors/
    In my last comment I reported the "evenNumbers" result containing odds was still showing:

    E.g. as archived today https://web.archive.org/web/20260318231310/https://dev.java/learn/api/streams/using-collectors/ ,
    it still shows the code snippet output as:

       Running this code gives you the following result.
       evenNumbers = [0, 1, 2, 3, 4]
    
    Image

    I can confirm that as of the latest, archived at https://web.archive.org/web/20260414161147/https://dev.java/learn/api/streams/using-collectors/ , this has been fixed and it now shows instead:

    No more duplicates = [0, 1, 2, 3, 4]
    

    I will take another set of snapshots and re: review these articles once more today and close this one by EOD if I don't find anything else or any regressions, thanks so much!

  9. JosePaumard commented on Apr 15, 2026

    @JosePaumard
    Contributor

    Thanks to you for your thorough reviews!

  10. willy-b commented on Apr 16, 2026

    @willy-b
    Author

    No problem!

    Found some more small items in this series you may be interested to fix, and at least one previously reported item which fell through the cracks.


    In https://dev.java/learn/api/streams/creating/ (archived as is at https://web.archive.org/web/20260415224219/https://dev.java/learn/api/streams/creating/ ):

    • (This typo was reported in the opening comment of this issue but has not been resolved yet)
      In the sentence

    Being able to process these lines with the Stream API gives you a more readable and maintainable code.

    "gives you a more readable and maintainable code" should be "gives you more readable and maintainable code".

    • In the sentence

    Note that, due to the limitation of the Playground, you cannot run this code in your browser.

    should "due to the limitation of the Playground" be "due to the limitations of" or "due to a limitation of"?

    • In the sentence

    If you need to refactor an existing code

    "refactor an existing code" should be "refactor existing code".

    • Re:

    Well the good news is that the Stream interface implements the AutoCloseable interface.

    Shouldn't this be "the Stream interface extends the AutoCloseable interface" or more precisely,
    "the Stream interface extends the BaseStream interface which extends the AutoCloseable interface",
    or "Well the good news is that AutoCloseable is a superinterface of Stream".

    My understanding is that interfaces are not said to implement other interfaces but rather extend them,
    and that is what is happening here:

    public interface Stream extends BaseStream<T, Stream>
    public interface BaseStream<T, S extends BaseStream<T,S>> extends AutoCloseable

    • In

    The Pattern class has also a method for you. What you can do is call Pattern.compile().splitAsStream(). Here is the code you can write using this method.

    I think "has also a method for you" might be more clear as "also has a method for you".
    In "Here is the code you can write using this method" , it might be clearer as "Here is code you can write using this method" or "Here is an example of code you can write using this method" as it can be applied many different ways


    In https://dev.java/learn/api/streams/intermediate-operation/ (archived as is at https://web.archive.org/web/20260416031953/https://dev.java/learn/api/streams/intermediate-operation/ ):

    • In the sentences

    The distinct() method can be used on unbound (infinite) streams, the sorted() method cannot.
    You can use the distinct() method on unbound streams, that will produce an unlimited number of objects, as long as your stream interrupts the elements produced by distinct(), as you can see it on the following example.

    I think "unbound" should be "unbounded" (As in e.g. the sentence describing this property of streams in https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/util/stream/package-summary.html:
    "Possibly unbounded. While collections have a finite size, streams need not. Short-circuiting operations such as limit(n) or findFirst() can allow computations on infinite streams to complete in finite time."
    Also, "that will produce" might be clearer as "which would produce".
    Finally, it seems "as you can see it on the following example" should be "as you can see in the following example".

    • In the sentence

    The fourth and last one is four that is rejected by the filtering step.
    "four that is rejected by the filtering step" should probably be "four which is rejected by the filtering step" (here "four" is italicized and refers to a particular instance of the word "four", not four of anything)

    The preceding sentence on the processing of the word "three" might also have some minor grammar issues.


    In https://dev.java/learn/api/streams/ (archived as is at https://web.archive.org/web/20260415223619/https://dev.java/learn/api/streams/ ):

    • In the sentence

    They will guide you all through the Stream API, starting at the basic concepts all the way to collector design and parallel streams.

    "all through" should probably be "through all of" (unless "API" is changed to "APIs" in which case "of" can be omitted)

    • Nit: The ending of sentences in the TOC is inconsistent, some section descriptions end with period and some do not. Most of the section descriptions end with a period, but three do not.

    Here are the descriptive sentences without ending periods (3 out of 12, should probably have a period added to be consistent with the others):

    • Implementing the map-filter-reduce algorithm
    • Understanding the characteristics of a stream
    • Implementing your own intermediate operations with the Gatherer API

    In https://dev.java/learn/api/streams/map-filter-reduce/ (archived as is at https://web.archive.org/web/20260415224027/https://dev.java/learn/api/streams/map-filter-reduce/ ):

    • In the sentence,

    The Stream API gives your four interfaces.

    "your" should be "you".

    • There is a missing space between the end of one sentence and the beginning of the next:

    DoubleStream.These


    Thanks so much!

    [edited only to fix first archival link which was not formatted properly, one can verify that the link itself and other content did not change in Github]

  11. JosePaumard commented on Apr 16, 2026

    @JosePaumard
    Contributor

    Thank you again for your report! I just pushed a fix.
    As for the . at the end of the section titles: I need to make a pass on these so that it's consistent all through the website. Thank you!

  12. willy-b commented on Apr 17, 2026

    @willy-b
    Author

    Thanks, here are more remaining items in the Streams tutorials (we are converging but not finished).


    In the https://dev.java/learn/api/streams/using-collectors/ article (archived as is at https://web.archive.org/web/20260416174816/https://dev.java/learn/api/streams/using-collectors/ ):

    • [reported earlier but not fixed yet]
      In this sentence

      It is created with the partitionningBy() factory method.

      "partitionningBy" should be "partitioningBy".

    • [reported earlier but not completely fixed yet]
      In the "Extracting an Ambiguous Maximum Value", the example which you reworked following my earlier question in Typos and possible small errors in the Streams articles ( https://dev.java/learn/api/streams/ ) #200 (comment) , the text for the first part of that example indicates that one of the strings of length 3 is expected to be removed to make the maximum ambiguous:

      The previous example was a nice example, because there was only one maximum value in your list. Unfortunately, real life cases are often not that nice, and you may have several key-value pairs that match the maximum value.
      Let us remove one element from the collection of the previous example.

      However, no length 3 string has actually been removed (yet; later code blocks show "one" removed but not here), so while it says the expected output now has 3 lengths tied for 4 strings:

      Printing this histogram gives you the following result.

      3 :: 3
      4 :: 3
      5 :: 3
      6 :: 2
      

      Now, as you can see, we have three key-value pairs for the maximum value.

      That does not match the actual current output, which still has an unambiguous maximum:

      
      3 :: 4 
      
      4 :: 3 
      
      5 :: 3 
      
      6 :: 2
      
      

      Because the strings have not had a length 3 example removed yet (later it is revealed it is the word "one" which should be removed here):

      Collection<String> strings =
        List.of("one", "two", "three", "four",
                "five", "six", "seven", "eight",
                "nine", "ten", "eleven", "twelve");
      
    Image

    In the following part of that extended example "Mapping with a Collector" both the actual and expected output include 4 length 3 strings still (the part with output result = [ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE]), but
    after that, in later parts of the example the string "one" is indeed removed so that the output matches the expected and the example demonstrates what is intended.


    In the latest version of https://dev.java/learn/api/streams/intermediate-operation/ (archived as is in https://web.archive.org/web/20260416174142/https://dev.java/learn/api/streams/intermediate-operation/ ), where my last comment had reported small errors, most items reported have been fixed but:

    • I previously reported:

      In the sentences

      The distinct() method can be used on unbound (infinite) streams, the sorted() method cannot.
      You can use the distinct() method on unbound streams, that will produce an unlimited number of objects, as long as your stream interrupts the elements produced by distinct(), as you can see it on the following example.

      I think "unbound" should be "unbounded" (As in e.g. the sentence describing this property of streams in https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/util/stream/package-summary.html:
      "Possibly unbounded. While collections have a finite size, streams need not. Short-circuiting operations such as limit(n) or findFirst() can allow computations on infinite streams to complete in finite time.")
      Also, "that will produce" might be clearer as "which would produce".
      Finally, it seems "as you can see it on the following example" should be "as you can see in the following example".

    This has been updated by the Oracle team so that "unbound" is now "unbounded", thanks!
    But it now says "as you can see on the following example" instead of "as you can see in the following example".


    In the latest version of https://dev.java/learn/api/streams/creating/ (archived as is at https://web.archive.org/web/20260416174306/https://dev.java/learn/api/streams/creating/ ), where my last comment had reported small errors, most items reported have been fixed but:

    • I had previously reported

    Re:

    Well the good news is that the Stream interface implements the AutoCloseable interface.

    Shouldn't this be "the Stream interface extends the AutoCloseable interface" or more precisely,
    "the Stream interface extends the BaseStream interface which extends the AutoCloseable interface", or "Well the good news is that AutoCloseable is a superinterface of Stream".

    My understanding is that interfaces are not said to implement other interfaces but rather extend them,
    and that is what is happening here:

    public interface Stream extends BaseStream<T, Stream>
    public interface BaseStream<T, S extends BaseStream<T,S>> extends AutoCloseable

    This was attempted to be fixed by your team, who now write

    Well the good news is that the Stream interface extends the BaseStream interface the AutoCloseable interface

    this is almost fixed but it is missing another "which extends" after "BaseStream interface".


    In the latest version of https://dev.java/learn/api/streams/terminal-operations/ (archived as is at https://web.archive.org/web/20260416174417/https://dev.java/learn/api/streams/terminal-operations/ )

    • In "Checking if the Elements of a Stream Match a Predicate",
      there is a demonstration of anyMatch which checks if any String in a stream has length exactly three,
      but it appears that the result was potentially mistakenly named, as the result variable is named "oneGT3" instead of "oneEQ3" (or similar):
    boolean oneGT3   =
       strings.stream()
              .anyMatch(s -> s.length() == 3);
    

    This seems like originally the predicate used for the demo may have been s -> s.length() > 3 instead of s -> s.length() == 3,
    but that the predicate was updated without updating the variable name before publishing.

    There is one other variable name that might be clearer in this example, which is allLT10, which suggests all less than 10 ,when it should be less than or equal to 10, or allLTE10:

    boolean allLT10  =
       strings.stream()
              .noneMatch(s -> s.length() > 10);
    

    If all Strings have length <= to 10, then none will match the predicate checking that the String has length greater than 10.

    • For the sentences:

    This collector creates an instance of ArrayList and adds the elements processed by your stream in it.
    So this pattern is not creating any side effect so there is no performance hit.

    • It may improve "adds the elements processed by your stream in it" to update it to "adds the elements processed by your stream to it" (using "to it" or "into it" as "adds to it" and "adds into it" are more standard than "adds in it")
    • It might be more typical English to write "is not creating any side effects" instead of "is not creating any side effect"

    In https://dev.java/learn/api/streams/custom-collectors/ (archived as is at https://web.archive.org/web/20260416174904/https://dev.java/learn/api/streams/custom-collectors/ ):

    In the examples on this page, only the 3-arg collect method ( https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/util/stream/Stream.html#collect(java.util.function.Supplier,java.util.function.BiConsumer,java.util.function.BiConsumer) ) is shown, not the 1-arg using the Collector interface.
    In the context of the Stream 3-arg collect method, as written there is potential for confusion between either the Collector interface or the combiner of a reducer and the 3-arg collect method combiners, between a BinaryOperator (or BiFunction) (used in the Collector interface combiner and reductions, has a return value) and a BiConsumer (used in the 3-arg collect method, has no return value, and updates a container):

    To be able to do that, the Collector API needs a third component, a combiner, that will merge these containers together.
    A combiner is modeled by an instance of BinaryOperator that takes two partially filled containers and returns one.
    This BinaryOperator is also modeled by a BiConsumer in the collect() overloads of the Stream API.

    The problem with this description of the combiner for the 3-arg collect method combiners shown on this page is that a BiConsumer is NOT a BinaryOperator as Consumers don't return anything (a BiFunction can be a BinaryOperator: a BiFunction with both input types and the output type all the same would be a BinaryOperator of that single type). This is unlike the analogous combiner for a reduce operation which does use a BinaryOperator/BiFunction.

    To see where this confusion "BinaryOperator is also modeled by a BiConsumer" (rather than being treated as different functional interfaces used by different collect combiners) probably comes from, compare the 3-argument Stream.collect method signature:

    R collect(Supplier supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R,R> combiner)

    and the Collector interface combiner (as discussed later in https://web.archive.org/web/20260416174917/https://dev.java/learn/api/streams/collector-interface/ ) ( https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/util/stream/Collector.html#combiner() )

    BinaryOperator combiner()

    or the analogous 3-argument reduce method signature:

    U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator combiner)

    Note that reduce uses a BinaryOperator for its combiner, but the 3-arg collect method uses a BiConsumer, which is NOT a BinaryOperator.

    So e.g. the existing sentence:

    A [collect] combiner is modeled by an instance of BinaryOperator that takes two partially filled containers and returns one.

    is valid for the Collector interface, but in the context of this page https://dev.java/learn/api/streams/custom-collectors/ (archived as is at https://web.archive.org/web/20260416174904/https://dev.java/learn/api/streams/custom-collectors/ ) should mention that for the 3-arg collect
    it uses "an instance of BiConsumer" instead of "BinaryOperator", and "that folds the elements from the second result container into the first result container" instead of "takes two partially filled containers and returns one" because the BiConsumer does not (cannot) return anything. See https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/util/stream/Stream.html#collect(java.util.function.Supplier,java.util.function.BiConsumer,java.util.function.BiConsumer) which says "The combiner function must fold the elements from the second result container into the first result container."

    Functional interfaces don't need to extend one another to be compatible as long as the signatures are compatible, but a BiConsumer doesn't return anything (operates via side-effects) unlike a BinaryOperator, so the void return and the non-void return are incompatible.

    To see this difference between the Collector interface combiner (as discussed later in https://web.archive.org/web/20260416174917/https://dev.java/learn/api/streams/collector-interface/ )
    and the Stream 3-arg collect method combiner,
    we can look at one of the 3-arg collect examples from this page https://web.archive.org/web/20260416174904/https://dev.java/learn/api/streams/custom-collectors/ (which is about 3-arg collect method usage, on IntStream and String and does not have Collector interface implementations despite the title), and one that uses Collection::addAll as the combiner which does not return the same type as the mutated collection:

    Supplier<List<Integer>> supplier                  = ArrayList::new;
    ObjIntConsumer<List<Integer>> accumulator         = Collection::add;
    BiConsumer<List<Integer>, List<Integer>> combiner = Collection::addAll;
    //BinaryOperator<List<Integer>> combiner = Collection::addAll; // cannot use BinaryOperator here but works as BiConsumer
    List<Integer> collect =
       IntStream.range(0, 10)
                .collect(supplier, accumulator, combiner );
    
    IO.println("collect = " + collect);
    

    The combiner is BiConsumer<List<Integer>, List<Integer>> combiner = Collection::addAll;,
    Collection::addAll has the wrong return type to even be used as a BinaryOperator, so it would fail with

    Error ⮕ incompatible types: bad return type in method reference boolean cannot be converted to java.util.List<java.lang.Integer>
    

    So the combiner would have to be changed if the above was written as an implementation of the Collector interface to pass to collect:

    // version using an anonymous Collector interface implementation, for a simple demo, not generic
    Collector<Integer, List<Integer>, List<Integer>> collector = new Collector<Integer, List<Integer>, List<Integer>>() {
    public Supplier<List<Integer>> supplier() {
            return ArrayList::new;
        }
    
        public BiConsumer<List<Integer>, Integer> accumulator() {
            return Collection::add;
        }
    
        public BinaryOperator<List<Integer>> combiner() {
            return (list1, list2) -> {
                 // unlike Stream 3-arg collect combiner which is a BiConsumer using side effects (updating left-arg to add right-arg into it)
                 // Collector interface uses a BinaryOperator that returns the combined value (like 3-arg reduce method)
                 List<Integer> combinedListToShowUsingReturnNotSideEffectsInInterfaceVersion = new ArrayList<>();  
                 combinedListToShowUsingReturnNotSideEffectsInInterfaceVersion.addAll(list1);
                 combinedListToShowUsingReturnNotSideEffectsInInterfaceVersion.addAll(list2);
                 return combinedListToShowUsingReturnNotSideEffectsInInterfaceVersion;
                };
        }
    
        public Function<List<Integer>, List<Integer>> finisher() {
            return Function.identity();
        }
    
        public Set<Characteristics> characteristics() {
            return Set.of(Characteristics.IDENTITY_FINISH);
        }
    };
    // have to add ".boxed()" as only 3-arg collect method is present for IntStream vs Stream which takes Collector interface
    // added `parallel()` to ensure that the combiner is actually used
    List<Integer> collect = IntStream.range(0, 10).boxed().parallel().collect(collector);
    
    IO.println("collect = " + collect);
    

    So my suggestion is to possibly emphasize that the 1-arg and 3-arg collect methods use different combiner types, without giving the impression that BinaryOperator (working via return type) can be generally substituted wherever one sees BiConsumer (working via side effects) (because it would not work, though some examples like StringBuffer::append suggest that it can as they both mutate the object and return a new reference).


    In the latest version of https://dev.java/learn/api/streams/reducing/ (archived as is at https://web.archive.org/web/20260416174325/https://dev.java/learn/api/streams/reducing/ )

    • In the sentence,

      The way this computation is conducted can be summarized on the following figure.

      "can be summarized on the following figure" should probably be "is summarized in the following figure".

    • There is a malformed sentence:

      The code is the following, and write some code that can use this method.

      Perhaps this was intended to be something like "Here is the code for our custom reduce method along with some code to show it in action".

    • In the sentence,

      Unfortunately, if you cannot find any faulty combination does not mean that your candidate is correct.

      "if you cannot find any faulty combination does not mean that your candidate is correct" should probably
      be "if you cannot find any faulty combination IT does not [necessarily] mean that your candidate is correct".


    In https://dev.java/learn/api/streams/characteristics/ (archived as is in https://web.archive.org/web/20260416174540/https://dev.java/learn/api/streams/characteristics/ ),

    In the sentence

    The fist example that may come to mind is any instance of the List interface.

    "The fist example" should be "The first example".

    Re:

    There are some others that, due to the way they are working, cannot produce collections with null values, like Map.values().

    This seems incorrect as Maps can have null values associated with its keys (and can have null keys) depending on the Map implementation.

    Indeed, in the example below using a HashMap, one observes null values are included in the Collection returned by Map.values() and the associated stream does not have the NONNULL characteristic:
    (this is true actually also for the keySet -- and the entrySet -- such that none of the Collections associated with a Map produce a Stream with the NONNULL characteristic at least for some implementations like HashMap meaning it cannot be a guaranteed property)

    jshell> import java.util.*;
    
    jshell> Map<String, Object> someMap = new HashMap<>();
    someMap ==> {}
    
    jshell> someMap.put("nonnull", new Object());
    $3 ==> null
    
    jshell> someMap.put("keyassociatedwithnull", null);
    $4 ==> null
    
    jshell> someMap
    someMap ==> {keyassociatedwithnull=null, nonnull=java.lang.Object@685cb137}
    
    jshell> someMap.values();
    $6 ==> [null, java.lang.Object@685cb137]
    
    jshell> someMap.values().stream().spliterator().hasCharacteristics(Spliterator.NONNULL)
    $7 ==> false
    

    Thanks!

  13. willy-b commented on Apr 18, 2026

    @willy-b
    Author

    Note, I edited my last comment to clarify the note I had added on possibly updating https://dev.java/learn/api/streams/custom-collectors/ (archived as is at https://web.archive.org/web/20260416174904/https://dev.java/learn/api/streams/custom-collectors/ )
    to emphasize the difference between the 3-arg collect method combiner vs 1-arg collect method combiner (via Collector interface) types. I switched the example I mentioned from the page to the one using the BiConsumer Collection::addAll as the combiner for the 3-arg collect method as unlike the StringBuffer::append example it cannot (in other contexts) be used as a BinaryOperator (StringBuffer:append mutates the StringBuffer AND returns a reference to it so it can be either a BiConsumer or BinaryOperator; whereas Collection::addAll mutates the collection and returns a different type, a boolean, so cannot be a BinaryOperator). I also mentioned that the focus on BinaryOperator would be appropriate for the later page https://web.archive.org/web/20260416174917/https://dev.java/learn/api/streams/collector-interface/ which is on the 1-arg collect method using the Collector interface (unlike the page I commented on). Other parts were not changed. The other items in the comment are clear errors or typos whereas this is a subjective comment on what might be clearer so feel free to focus on the other items and ignore it unless you have time. Thanks.

  14. 32 remaining items

  15. JosePaumard commented on Jun 25, 2026

    @JosePaumard
    Contributor

    Thank you for your report! A fix is on its way.

  16. authentictech commented on Jun 25, 2026

    @authentictech

    I thought I'd put this here rather than opening a new issue. I didn't see this mentioned above.

    On https://dev.java/learn/api/streams/gatherers/#interrupt in the Fixing the Distinct Gatherer section:

    Indeed, your integrator is currently been called, to it is bound to be false, and you did not call downstream.push(), so it could not change.

    Should probably be:

    Indeed, your integrator is currently being called, so it is bound to be false, and you did not call downstream.push(), so it could not change.

  17. JosePaumard commented on Jun 25, 2026

    @JosePaumard
    Contributor

    Thank you again for your report! A fix is on its way.

  18. willy-b commented on Jun 27, 2026

    @willy-b
    Author

    Thanks to both of you! Here are a few more items I have uncovered during additional review.


    In the latest version of https://dev.java/learn/api/streams/gatherers/ (archived as is at https://web.archive.org/web/20260626201901/https://dev.java/learn/api/streams/gatherers/ )

    you may wish to double check that "by just calling parallel() on an existing stream" was not intended to be "just by calling parallel() on an existing stream".

    • In the sentence

      There are five factory methods in it, that create ready to use gatherers that you can create in your application.

      you may wish to double check that "that create ... that you can create in your application" was not intended to be "that create ... that you can use in your application" (replacing second "create" in the sentence with "use").

    • In the sentence

      Note that for this gatherer, all the windows produce have the same size, as long as the upstream produces more elements that the size of the window you chose.

      "all the windows produce have" should be "all the windows produced have" ('produce" seems to have been intended to be "produced").


    Thanks so much!

  19. JosePaumard commented on Jun 29, 2026

    @JosePaumard
    Contributor

    Thank you again for your report! A fix is on its way.

  20. willy-b commented on Jul 1, 2026

    @willy-b
    Author

    Thanks for fixing the broken links and other items above.
    Here are more items I have found during additional careful review (no AI assistance or LLM is used) of this tutorial series, which you may care to fix.


    In the latest version of https://dev.java/learn/api/streams/using-collectors/ (archived as is at https://web.archive.org/web/20260630220135/https://dev.java/learn/api/streams/using-collectors/?no_cache=true )


    In the latest version of https://dev.java/learn/api/streams/creating/ (archived as is https://web.archive.org/web/20260629215321/https://dev.java/learn/api/streams/creating/?no_cache=true ),


    In the latest version of https://dev.java/learn/api/streams/reducing/ (archived as is at https://web.archive.org/web/20260629223611/https://dev.java/learn/api/streams/reducing/?no_cache=true )

    • In the sentence

      All these operations are called terminal operations in the Stream API and consists in reducing your stream.

      "All these operations ... consists" should be "All these operations ... consist".

    • In the sentence

      Then take the next element and sum it to the partial sum you have computed.

      "sum it to" may be clearer than "add it to" for adding just two elements.
      "sum it to" is a much rarer phrasing:

    Image
    • In the sentence

      The way this computation is conducted is summarized is the following figure.

      "is summarized is the following figure" should be "is summarized in the following figure" (3rd "is" in sentence should be substituted with "in").

    • In the discussion of the third overload of reduce() , no link to the documentation is provided:

      Fusing Mapping and Reduction in one Method
      The third one is a little more complex. It combines an internal mapping and a reduction with several parameters.
      Let us examine the signature of this method.

      <U> U reduce(U identity,
              BiFunction<U, ? super T, U> accumulator,
              BinaryOperator<U> combiner);
      

      This method works with a type U that is defined locally to this method and used by the binary operator. The binary operator works in the same way as in the previous overloads of the reduce() method, except that it is not applied to elements of the stream but merely to mapped versions of them.

      "The third one" is mentioned, as well as "this method", without links, then at the end of this block, when there is a link to "reduce()" it is to a different, simpler overload.
      It would be easier for the reader in my opinion if there were a link to this third overload of reduce() so the reader could easily cross-reference the official documentation.


    In the latest version of https://dev.java/learn/api/streams/intermediate-operation/ (archived as is at https://web.archive.org/web/20260629211109/https://dev.java/learn/api/streams/intermediate-operation/?no_cache=true ),

    • In the sentence

      You can use the distinct() method on unbounded streams, that will produce an unlimited number of objects, as long as your stream interrupts the elements produced by distinct(), as you can see in the following example.

      "that will produce an unlimited number of objects" should probably be "producing an unlimited number of objects".


    In the latest version of https://dev.java/learn/api/streams/terminal-operations/ (archived as is at https://web.archive.org/web/20260630185036/https://dev.java/learn/api/streams/terminal-operations/ ),

    • In the discussion of anyMatch, allMatch, noneMatch

      anyMatch(predicate): returns true if one element of the stream is found, that matches the given predicate.
      allMatch(predicate): returns true if all the elements of the stream match the predicate.
      noneMatch(predicate): returns true if none of the elements match the predicate.

      the behavior for empty streams is not provided, which would be useful for readers as it is perhaps counterintuitive:
      For an empty stream, anyMatch returns false, but both allMatch and noneMatch return true for any given Predicate.

      (That is to say, the Stream interface JavaDoc says that when the stream is empty,
      for any given predicate, allMatch, noneMatch, and anyMatch behave as if

      • all of the elements match (allMatch returns true) AND
      • none of the elements match (noneMatch returns true)
      • BUT that it is not true that any of the elements match (anyMatch returns false).
        Perhaps these results are not obvious as in other cases allMatch implies anyMatch and either contradicts noneMatch.)

    In the latest version of https://dev.java/learn/api/streams/custom-collectors/ (archived as is at https://web.archive.org/web/20260630225938/https://dev.java/learn/api/streams/custom-collectors/?no_cache=true )

    • In the sentence
      (may have been added by the dev.java team recently, as there have been many edits to this part,
      I do NOT propose the edits and only look for errors after they have already gone live, NOT involved in review beforehand)

      Note that you can also collect a Stream by passing these components to the Stream.collect(supplier, combiner, accumulator) method.

      the method linked by "Stream.collect(supplier, combiner, accumulator)"
      is <R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R,R> combiner),
      so the argument labels have been given out of order, it should be supplier, accumulator, then combiner.

      In a subsequent sentence, they are already given in the correct order:

      In the stream.collect(supplier, accumulator, combiner) method the combiner is of type BiConsumer.

    • In the sentence

      A bifunction, used to merge the two containers created by the two downstream collector.

      "collector" should be "collectors".


    In the latest version of https://dev.java/learn/api/streams/optionals/ (archived as is in https://web.archive.org/web/20260701023916/https://dev.java/learn/api/streams/optionals/?no_cache=true )

    • In the sentence (this is not an error, but something that could potentially confuse some readers, so noting it)

      Two methods are there for you to test your optional object: isPresent(), and isEmpty(), added in Java SE 11.

      If the release these methods were added in is important, you may consider to clarify this so that nobody misinterprets this to mean that isPresent() was added in Java SE 11 (it was present earlier, e.g. in Java 8 https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html#isPresent-- ).
      This could be accomplished by modifying the sentence to say "isPresent() which was added in Java SE 8, and isEmpty(), which was added in Java SE 11."

    • In the sentence (this is not an error, but something that could potentially confuse some readers, so noting it)

      You are not interested in the pairs where the two authors are in fact the same author; a pair of two authors (A1, A2) is the same as the pair (A2, A1)

      while "(A1, A2)" and "(A2, A1)" are clear, the words around them could be clearer.
      It may be useful to update just that part to say "a pair of two authors is the same regardless of order: (A1, A2) is the same pair as (A2, A1)".

    • In the sentence

      This patterns maps a Stream<Map.Entry<Integer, Optional<Map.Entry<PairOfAuthors, Long>>>> with empty optionals to a Stream<Map.Entry<Integer, Map.Entry<PairOfAuthors, Long>>>, removing all the key/value pairs that had empty optionals.

      "This patterns maps" should be "This pattern maps".

    • In the sentence

      You can directly query the instance you have and open it if there is something in it, or you can use stream-like methods on it:
      map(), flatMap(), filter(), and even an equivalent of forEach().

      the first three methods are methods available on the Optional class that are similar to Stream methods, but the last one "forEach()" is a link to a Stream method.
      Presumably, the "equivalent of forEach()" is referring to ifPresent() on Optional as there is no "forEach" on the Optional class.
      To avoid confusion, since ifPresent() is not currently mentioned until nearly the end of the page,
      it may be useful to replace "and even an equivalent of forEach()"
      WITH "and ifPresent() , an equivalent of the Stream method forEach()".


    In the latest version of https://dev.java/learn/api/streams/parallel-streams/ (archived as is at https://web.archive.org/web/20260701011932/https://dev.java/learn/api/streams/parallel-streams/?no_cache=true )

    • In the text and on the sidebar, the heading

      Optimizing Streams Computations

      "Streams Computations" should be "Stream Computations".

    • In the sentences

      All these structures are used in the Collections Framework, and you can get the number of elements each of them carry.
      This is not the case for all the structures from which you can create a stream.
      Splitting the Lines of a Text File
      This is the case for the Files.lines(path) pattern, which was covered earlier in this tutorial. It creates a stream that processed the lines of the text file denoted by this path object.
      It is not possible to get the number of lines of a text file without analyzing it.

      The use of "This is not the case" or "This is the case" here is unclear and seemingly inconsistent.
      While the overall meaning is clear enough in the context of this section, I would humbly suggest that it may be despite the writing here rather than because of it.
      "This is not the case for all the structures..." refers to the fact that it is not generally true that one can get the number of elements in advance.
      But in the very next sentence "This is the case for the Files.lines(path) pattern ... It is not possible to get the number of lines of a text file without analyzing it" , "the case" seems to mean the opposite thing.

      I would suggest to get rid of the use of "This is not the case" and "This is the case" and be specific.
      E.g. replacing "This is not the case for all the structures from which you can create a stream"
      with something like "The number of elements is not available in advance for all the structures from which you can create a stream."
      Then also replace "This is the case for the Files.lines(path) pattern ... It is not possible to get the number of lines of a text file without analyzing it",
      with something like "For example, in the Files.lines(path) pattern the number of lines are not available in advance ... It is not possible to get the number of lines of a text file without analyzing it."
      Thanks!

    • In the sentence

      Having such stateful operations leads to overheads in parallel streams.

      "overheads" should be "overhead".

    • In the sentence

      Let us examine all these steps on by one and see what can prevent you from getting better performance gains.

      "on by one" should be "one by one"


    Thanks very much!

  21. JosePaumard commented on Jul 1, 2026

    @JosePaumard
    Contributor

    Thank you again for your report! A fix is on its way.

  22. willy-b commented on Jul 17, 2026

    @willy-b
    Author

    Thanks so much!


    In the latest version of https://dev.java/learn/api/streams/optionals/ (archived as is at https://web.archive.org/web/20260717045756/https://dev.java/learn/api/streams/optionals/?nonce=2 )

    • In the sentence

      Knowing the two authors that published to most together can be done from a histogram in which the keys are the pairs of authors and the values the number of articles they wrote together.

      "that published to most together" should be "that published the most together" ("to" should be "the").

    • Nit (optional): In the sentence

      The Optional class is a final class with private constructor.

      it seems "with private constructor" should be "with only a private constructor".


    In the latest version of https://dev.java/learn/api/streams/characteristics/ (archived as is at https://web.archive.org/web/20260717011211/https://dev.java/learn/api/streams/characteristics/?nonce=2 ) ,

    • In the sentence
      " There are structures from the Collection Framework that do not accept null values,"

      "Collection Framework" should be "Collections Framework".


    In the latest version of https://dev.java/learn/api/streams/using-collectors/ (archived as is at https://web.archive.org/web/20260716093742/https://dev.java/learn/api/streams/using-collectors/?nonce=1 ) ,


    In the latest version of https://dev.java/learn/api/streams/creating/ (archived as is at https://web.archive.org/web/20260716092501/https://dev.java/learn/api/streams/creating/?nonce=1 )

    • In the sentence

      The iterate() method stops generating elements when this predicate becomes false.

    the author has linked the wrong overload for the context; "iterate()" links to https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/util/stream/Stream.html#iterate(T,java.util.function.UnaryOperator) ,
    which is the 2-arg overload when the overload discussed is the 3-arg which takes an additional Predicate, so it should link to https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/util/stream/Stream.html#iterate(T,java.util.function.Predicate,java.util.function.UnaryOperator) .


    In the latest versions of https://dev.java/learn/api/streams/terminal-operations/ (archived as is at https://web.archive.org/web/20260716094428/https://dev.java/learn/api/streams/terminal-operations/ )

    Thanks for adding the description of the behavior for empty streams for anyMatch, allMatch, noneMatch:

    • anyMatch(predicate): returns true if one element of the stream is found, that matches the given predicate. This method returns false for an empty stream.
    • allMatch(predicate): returns true if all the elements of the stream match the predicate. This method returns true for an empty stream.
    • noneMatch(predicate): returns true if none of the elements match the predicate. This method returns true for an empty stream.

    As far as the change to show the empty stream behavior in the subsequent code example, I have a thought you may care to respond to or ignore per your preference. Though technically sufficient, the new demonstrations of anyMatch, allMatch, noneMatch on empty streams use different predicates; while the predicate is irrelevant on the empty streams the changing predicate may miss making it clear that for the same predicate allMatch and noneMatch will both be simultaneously true for empty streams, and anyMatch will be false (which is counterintuitive). But with the addition of the information on empty stream behavior to the anyMatch/allMatch/noneMatch introductions the original concern is solved.


    Thanks!

  23. JosePaumard commented on Jul 18, 2026

    @JosePaumard
    Contributor

    Thank you again for your report! A fix is on its way.

  24. willy-b commented on Jul 20, 2026

    @willy-b
    Author

    Thanks for the excellent updates to the empty stream examples for the anyMatch, allMatch, noneMatch stream operations! Those are well covered now.

    Here are a few remaining other items I found in the latest versions of these Stream tutorial pages.


    In the latest version of https://dev.java/learn/api/streams/gatherers/ (archived as is at https://web.archive.org/web/20260720003101/https://dev.java/learn/api/streams/gatherers/?no_cache=true )

    • In the sentences

      Gatherer.of(initializer,integrator,combiner,finisher). This is the pattern we are going to cover now. [...]
      It makes the corresponding factory method a little more complex, as you need to provide a fourth parameter, called a combiner, that can combine two instances of your mutable state.

      the combiner is NOT the fourth parameter but rather the third (it is true that given also the initializer, integrator, and finisher there are four total) so "you need to provide a fourth parameter, called a combiner" is not accurate. Maybe it could be reworded as "you need to provide a combiner as the third parameter, leading to a total of four parameters" or something.

    • In the sentence

      It is our old friend the good NullPointerException that nobody expects.

      I think "our good old friend the NullPointerException" was intended instead of "our old friend the good NullPointerException" (NPEs are not good and the phrase is "good old friend")

    Image

    In the latest version of https://dev.java/learn/api/streams/parallel-streams/ (archived as is at https://web.archive.org/web/20260719194140/https://dev.java/learn/api/streams/parallel-streams/?nonce=1 ),

    • In the sentence

      Once you have chosen to go parallel, then you need to make sure the performances of your stream computations have indeed improved.

      "the performances of ... have indeed improved" should be "the performance of ... has indeed improved".

    • In the sentence

      These two elements will hit the performance of your parallel streams.

      "hit the performance of" is an unusual phrasing (though one often hears of "taking a performance hit due to "). I would humbly suggest to rephrase this to "negatively impact the performance of".

    Image

    In the latest version of https://dev.java/learn/api/streams/optionals/ (archived as is at https://web.archive.org/web/20260719122505/https://dev.java/learn/api/streams/optionals/?nonce=11 )


    Thanks so much!

  25. JosePaumard commented on Jul 20, 2026

    @JosePaumard
    Contributor

    Thank you again for your report! A fix is on its way.

  26. willy-b commented on Jul 21, 2026

    @willy-b
    Author

    Can you confirm the changes were deployed? I don't see the updates yet, thanks!

  27. JosePaumard commented on Jul 24, 2026

    @JosePaumard
    Contributor

    Sorry for the delay. It should be live in a few mn now.

  28. willy-b commented on Jul 25, 2026

    @willy-b
    Author

    Thanks, almost all items reported above were resolved.

    The update made to the latest version of https://dev.java/learn/api/streams/optionals/ (archived as is at https://web.archive.org/web/20260724224757/https://dev.java/learn/api/streams/optionals/?nonce=1 ) is not quite complete:

    • In the sentence

      and even ifPresent(), which is analogous to of Stream.forEach().

      the "of" in "analogous to of" should still be removed.

    Thanks!

  29. willy-b commented on Aug 11, 2026

    @willy-b
    Author

    Thanks for fixing the item mentioned in my last comment!


    In the latest version of https://dev.java/learn/api/streams/map-filter-reduce/ (archived as is at https://megalodon.jp/2026-0812-0021-17/https://dev.java:443/learn/api/streams/map-filter-reduce/ as web.archive.org is not storing snapshots for me today )

    • In the sentence

      You can recognize another map-filter-reduce processing on a list of cities.

      "another map-filter-reduce processing on a list of cities" might be clearer as "another map-filter-reduce processing of a list of cities" or some other phrasing.

    • In the following lines from a code example on that page (note in megalodon snapshot one may need to check the HTML as the code snippets do not display)

      List<City> cities = // [...]
      // [...]
      // as there is no map() on the Collection interface
      

    It may be clearer to write "no map() on the List (nor Collection) interface" as the variable in question is an instance of List (which extends Collection), not Collection.


    In the latest version of https://dev.java/learn/api/streams/intermediate-operation/ (archived as is at https://megalodon.jp/2026-0812-0023-22/https://dev.java:443/learn/api/streams/intermediate-operation/ ),

    • In the line of code (occurring identically in two of the examples), (note in megalodon snapshot one may need to check the HTML as the code snippets do not display)

      City newYork= new City("New York", 8_258);
      

      it appears that it is unintentionally the case that "newYork=" is missing a space between "newYork" and "=" (doesn't break anything, not a blocker) (not the case for any of the other cities listed)

    • In the following sentences referring to Stream.mapMulti:

      But you can also use this special syntax where you put this type before calling mapMulti(). It is not a syntax that you see very often in Java code.
      You can use it in a static and nonstatic context.

      Re

      You can use it in a static and nonstatic context.

    Nothing about using mapMulti in a static vs non-static context is to my knowledge mentioned in the API documentation for mapMulti (https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/stream/Stream.html#mapMulti(java.util.function.BiConsumer). mapMulti is a non-static instance method, so I wonder if this statement is in error or could perhaps be clarified (I am probably not the only person who finds it confusing). It occurs in part of the text that has been added recently by the dev.java team to explain what is explained in the API documentation as:

    If a lambda expression is provided as the mapper function argument, additional type information may be necessary for proper inference of the element type of the returned stream.
    This can be provided in the form of explicit type declarations for the lambda parameters or as an explicit type argument to the mapMulti call.

    which does not mention static contexts. The rest of the explanation is in my opinion clear.

    • In the sentence

      It is not the case for sorted(), that needs to see all the elements produced before producing them.

      "needs to see all the elements produced before producing them" would probably be clearer as "needs to see all the elements produced before processing them" (with "before producing them" replaced with "before processing them", or maybe with "before forwarding the sorted version of the input elements").


    In the latest version of https://dev.java/learn/api/streams/creating/ (archived as is at https://web.archive.org/web/20260811013427/https://dev.java/learn/api/streams/creating/?nonce=32 )

    • In the list item

      the elements created by splitting a string of characters with a regular expressions;

      "with a regular expressions" should be "with a regular expression".


    On https://dev.java/learn/api/streams/optionals/ (archived as is at https://megalodon.jp/2026-0812-0015-56/https://dev.java:443/learn/api/streams/optionals/?nonce=30 ) http://web.archive.org is refusing to archive it today)


    Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions