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