Skip to content

fix(java): support TreeSet/TreeMap subclasses without Comparator constructor in SortedSet/SortedMapSerializer#3344

Merged
chaokunyang merged 2 commits intoapache:mainfrom
mandrean:treeset-subclass-comparator
Feb 17, 2026
Merged

fix(java): support TreeSet/TreeMap subclasses without Comparator constructor in SortedSet/SortedMapSerializer#3344
chaokunyang merged 2 commits intoapache:mainfrom
mandrean:treeset-subclass-comparator

Conversation

@mandrean
Copy link
Contributor

@mandrean mandrean commented Feb 16, 2026

Why?

SortedSetSerializer and SortedMapSerializer unconditionally require a (Comparator) constructor for all TreeSet/TreeMap subclasses. Java constructors are not inherited, so a class ChildTreeSet extends TreeSet<String> (or class ChildTreeMap extends TreeMap<String, String>) with only a no-arg constructor throws UnsupportedOperationException when registered with the corresponding serializer.

What does this PR do?

Changes both SortedSetSerializer and SortedMapSerializer to try the (Comparator) constructor first, and fall back to the no-arg constructor if not found. At deserialization time, uses whichever constructor is available. The no-arg fallback uses natural ordering, which is the common case for TreeSet/TreeMap subclasses.

Only throws UnsupportedOperationException if neither constructor is found.

Related issues

Does this PR introduce any user-facing change?

  • Does this PR introduce any public API change?
    No. Existing behavior is preserved for classes that have a (Comparator) constructor. Classes that previously threw UnsupportedOperationException now work.

  • Does this PR introduce any binary protocol compatibility change?
    No. The write path is unchanged (size + comparator ref), and the read path still reads the same bytes in the same order. Only the post-read instantiation strategy differs (which constructor is used). Wire format is identical in both directions.

Benchmark

Not applicable; the constructor lookup happens once at serializer construction time, not per serialization. The runtime paths (newCollection/newMap) add a single null check (if (comparatorConstructor != null)) which is negligible.

@mandrean mandrean force-pushed the treeset-subclass-comparator branch from db9b3e4 to 95a5eed Compare February 16, 2026 12:48
@chaokunyang
Copy link
Collaborator

It seems that the comparator is null if the treeset subclass don't pass comparator to parent class. In such cases, treeset Or treemap will just use key compareTo api for sort. So it's not a bug, could you remove the log and keep constructor branch. And could you ask check TreeMap? I think it has similar issue

@mandrean mandrean force-pushed the treeset-subclass-comparator branch from 95a5eed to ecf8f6d Compare February 17, 2026 08:26
@mandrean mandrean changed the title fix(java): Support TreeSet subclasses without Comparator constructor in SortedSetSerializer fix(java): support TreeSet/TreeMap subclasses without Comparator constructor in SortedSet/SortedMapSerializer Feb 17, 2026
@mandrean
Copy link
Contributor Author

Thanks, pushed some changes, have a look again @chaokunyang!

@mandrean mandrean requested a review from chaokunyang February 17, 2026 08:34
Copy link
Collaborator

@chaokunyang chaokunyang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great!

@chaokunyang chaokunyang merged commit 4f4453d into apache:main Feb 17, 2026
58 checks passed
mandrean added a commit to mandrean/fory that referenced this pull request Mar 26, 2026
…classes

Adds optimized child-serializers for subclasses of TreeSet,
ConcurrentSkipListSet, PriorityQueue, TreeMap, and ConcurrentSkipListMap.
Previously these fell back to JDK-compatible serialization because the
hierarchy walk hit writeObject/readObject on the parent JDK type.

The new serializers handle comparator preservation, slot field
serialization, and ref-tracking (including the deferred-ref pattern for
concurrent variants).

Constructor discovery is centralized in ContainerConstructors, which
probes for all standard constructor shapes and gracefully falls through
to natural ordering when no comparator-preserving constructor is
available (per apache#3344 (comment)).

Existing SortedSetSerializer, SortedMapSerializer, and
PriorityQueueSerializer are refactored to reuse the same factory logic.
mandrean added a commit to mandrean/fory that referenced this pull request Mar 26, 2026
Covers all constructor variants for TreeSet, ConcurrentSkipListSet,
PriorityQueue, TreeMap, and ConcurrentSkipListMap child classes:
- (Comparator), (SortedSet)/(SortedMap) preserve comparator
- (), (Collection)/(Map), (int) use natural ordering
- Unsupported constructors and custom-serialized children fall back
- Async compilation path verified
- InternalComparatorTreeSet verifies the graceful fall-through from
  apache#3344 (comment)

Also adds (Collection) and (SortedSet)/(SortedMap) constructor tests
for the existing SortedSetSerializer and SortedMapSerializer.
mandrean added a commit to mandrean/fory that referenced this pull request Mar 26, 2026
…classes

Adds optimized child-serializers for subclasses of TreeSet,
ConcurrentSkipListSet, PriorityQueue, TreeMap, and ConcurrentSkipListMap.
Previously these fell back to JDK-compatible serialization because the
hierarchy walk hit writeObject/readObject on the parent JDK type.

The new serializers handle comparator preservation, slot field
serialization, and ref-tracking (including the deferred-ref pattern for
concurrent variants).

Constructor discovery is centralized in ContainerConstructors, which
probes for all standard constructor shapes and gracefully falls through
to natural ordering when no comparator-preserving constructor is
available (per apache#3344 (comment)).

Existing SortedSetSerializer, SortedMapSerializer, and
PriorityQueueSerializer are refactored to reuse the same factory logic.
mandrean added a commit to mandrean/fory that referenced this pull request Mar 26, 2026
Covers all constructor variants for TreeSet, ConcurrentSkipListSet,
PriorityQueue, TreeMap, and ConcurrentSkipListMap child classes:
- (Comparator), (SortedSet)/(SortedMap) preserve comparator
- (), (Collection)/(Map), (int) use natural ordering
- Unsupported constructors and custom-serialized children fall back
- Async compilation path verified
- InternalComparatorTreeSet verifies the graceful fall-through from
  apache#3344 (comment)

Also adds (Collection) and (SortedSet)/(SortedMap) constructor tests
for the existing SortedSetSerializer and SortedMapSerializer.
mandrean added a commit to mandrean/fory that referenced this pull request Mar 26, 2026
Covers all constructor variants for TreeSet, ConcurrentSkipListSet,
PriorityQueue, TreeMap, and ConcurrentSkipListMap child classes:
- (Comparator), (SortedSet)/(SortedMap) preserve comparator
- (), (Collection)/(Map), (int) use natural ordering
- Unsupported constructors and custom-serialized children fall back
- Async compilation path verified
- InternalComparatorTreeSet verifies the graceful fall-through from
  apache#3344 (comment)

Also adds (Collection) and (SortedSet)/(SortedMap) constructor tests
for the existing SortedSetSerializer and SortedMapSerializer.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Java] SortedSet/SortedMapSerializer fails for TreeSet/TreeMap subclasses without Comparator constructor

2 participants