On Mon, 23 Feb 2026 14:40:57 GMT, Oli Gillespie <[email protected]> wrote:

>> That case still fails, yes, but I'm not totally sure why. I'm looking into 
>> it.
>> 
>> 
>> java.util.ConcurrentModificationException
>>      at 
>> java.base/java.util.TreeMap$NavigableSubMap$SubMapIterator.prevEntry(TreeMap.java:2070)
>>      at 
>> java.base/java.util.TreeMap$NavigableSubMap$DescendingSubMapEntryIterator.next(TreeMap.java:2121)
>>      at 
>> java.base/java.util.TreeMap$NavigableSubMap$DescendingSubMapEntryIterator.next(TreeMap.java:2114)
>>      at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133)
>>      at 
>> java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1939)
>>      at 
>> java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:570)
>>      at 
>> java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:560)
>>      at 
>> java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:635)
>>      at 
>> java.base/java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:291)
>>      at 
>> java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:652)
>>      at 
>> java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:658)
>>      at 
>> org.openjdk.tests.java.util.stream.CollectionAndMapModifyStreamTest.testEntrySetSizeRemove(CollectionAndMapModifyStreamTest.java:164)
>>      at 
>> org.openjdk.tests.java.util.stream.CollectionAndMapModifyStreamTest.testMapEntriesSizeRemove(CollectionAndMapModifyStreamTest.java:155)
>
> Oh I understand now. The default Set spliterator is `Spliterator<T> 
> spliterator(Collection<? extends T> c, int characteristics)`. It doesn't 
> create an iterator until forEachRemaining is called, which in the test is 
> *after* the .remove modification, so it doesn't observe a discrepancy. The 
> new implementation uses creates the iterator up-front to pass to 
> `spliteratorUnknownSize`, so in that case the iterator is created before the 
> modification, hence CME.

It ends up something like this:


public static void main(String[] args) {
    List<String> strings = new LinkedList<>();
    strings.add("foo");
    strings.add("bar");

    Spliterator<String> s = Spliterators.spliterator(strings, 
Spliterator.DISTINCT); // Don't create iterator yet
    strings.remove(strings.iterator().next());
    s.forEachRemaining(System.out::println); // Spliterator creates iterator 
here, after the .remove call. No CME

    s = Spliterators.spliteratorUnknownSize(strings.iterator(), 
Spliterator.DISTINCT); // Eagerly create the iterator
    strings.remove(strings.iterator().next()); // Modifying after the iterator 
was created
    s.forEachRemaining(System.out::println); // ConcurrentModificationException
}


So it's just a side effect of `spliteratorUnknownSize` needing the iterator to 
be created already. I think the test skip is valid, then - it's true that this 
case is no longer lazy like it was.

-------------

PR Review Comment: https://git.openjdk.org/jdk/pull/28608#discussion_r2841246021

Reply via email to