-
-
Notifications
You must be signed in to change notification settings - Fork 181
Use MethodHandle in processing related to value class
#1018
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Errors thrown by the constructor of value classes are no longer wrapped in InvocationTargetException.
Errors thrown by the constructor of value classes are no longer wrapped in InvocationTargetException.
with deprecation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR replaces reflective invocation with MethodHandle for value‐class (un)boxing to improve performance and consistency, updates serializers/deserializers to use these handles, and adjusts tests to expect IllegalArgumentException instead of InvocationTargetException.
- Migrate caches and converters in
ReflectionCacheto useClass<*>keys andMethodHandle‐based converters. - Update serializers/deserializers (
KotlinSerializers,KotlinKeySerializers,KotlinDeserializers, etc.) to use new handle‐based implementations. - Adjust tests in
WithoutCustomDeserializeMethodTest.ktto expectIllegalArgumentExceptionand compare it directly.
Reviewed Changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/test/kotlin/.../WithoutCustomDeserializeMethodTest.kt (mapKey) | Updated assertThrows target and exception assertion to IllegalArgumentException. |
| src/test/kotlin/.../WithoutCustomDeserializeMethodTest.kt | Same test update for standalone value class. |
| src/main/kotlin/.../ReflectionCache.kt | Changed cache key types and added unbox/box converter caches. |
| src/main/kotlin/.../KotlinSerializers.kt | Switched ValueClassSerializer to handle classes via MethodHandle. |
| src/main/kotlin/.../KotlinModule.kt | Pass ReflectionCache into serializers/key‐serializers. |
| src/main/kotlin/.../KotlinKeySerializers.kt | Refactored key serializers to use handle‐based implementation. |
| src/main/kotlin/.../KotlinKeyDeserializers.kt | Introduced handle‐based key deserializers with specialized wrappers. |
| src/main/kotlin/.../KotlinDeserializers.kt | Updated value‐class deserializers to use MethodHandle, split by conversion type. |
| src/main/kotlin/.../InternalCommons.kt | Added shared MethodType constants and handle utilities. |
| src/main/kotlin/.../Converters.kt | Rewrote converters to ValueClassBoxConverter/UnboxConverter with MethodHandle. |
| pom.xml | Updated javadoc exclusions for new/renamed internal classes. |
Comments suppressed due to low confidence (1)
src/main/kotlin/com/fasterxml/jackson/module/kotlin/ReflectionCache.kt:41
- The LRUMap is initialized with
0as the initial capacity andreflectionCacheSizeas the max, but originally both parameters were set toreflectionCacheSize. Using0may lead to immediate eviction or unexpected behavior. ConsiderLRUMap(reflectionCacheSize, reflectionCacheSize)to match its intended capacity.
LRUMap(0, reflectionCacheSize)
...odule/kotlin/kogeraIntegration/deser/valueClass/mapKey/WithoutCustomDeserializeMethodTest.kt
Show resolved
Hide resolved
MethodHandle in processing related to value classMethodHandle in processing related to value class
Background
To seamless handling of value classes with Jackson, reflection is heavily used in the related processing.
In many cases, multiple reflective calls are performed in sequence.
For example, during deserialization, at least two reflective calls are required: invoking the primary constructor and then a call to
box.Similarly, serialization typically requires at least two reflective calls — one to
boxand one tounbox.Replacing these reflective operations with MethodHandles is expected to performance improvement.
Modification details
Each of the parts that used to use
Methodare replaced withMethodHandle.In particular, the parts of the process in
kotlin-modulewhereMethodwas called continuously are further optimized by composingMethodHandleusingMethodHandle.filterReturnValue.In addition, for
Int,Long,String, and (Java)UUID, which are particularly common types wrapped invalue class, separate optimizations are made to speed up invocation by making the types explicit.This change is based on the implementation in
jackson-module-kogera 2.19.0-beta25.https://github.com/ProjectMapK/jackson-module-kogera/releases/tag/2.19.0-beta25
It also fixes a problem where the
unbox-implmethod was not properly cached.In particular, the problem of getting
unbox-implon every run in several cases related to serialization has been fixed, which will further greatly increase throughput in those cases.Breaking change
Due to a change in execution, exceptions thrown by the constructor of
value classare no longer wrapped in anInvocationTargetException.Benchmark
The following repository was used for benchmarking:
k163377/jackson-value-class-benchmark
The commits to be compared are f54ef6e(before improvement) and d81fb82(after improvement).
Benchmark Details
The benchmark should compare performance with and without the aforementioned type-specific optimization.
Additionally, the benchmark was designed to minimize the overall processing done by
Jacksonitself, in order to better isolate and observe the impact of theMethodHandlebased performance enhancements.With these considerations in mind, the benchmarks were structured as follows:
value classtypes were tested: one wrapping anInt(with type-specific optimization), and one wrapping aShort(without type-specific optimization)Since the benefits of this optimization increase with the number of
value classproperties involved, this benchmark serves as a lower bound for the performance improvements achievable invalue classhandling.However, a preliminary check by
kogeradid not explicitly confirm the effect of type-specific optimizations in this benchmark because the percentage of processing related tovalue classwas too low.This is summarized in the following article(sorry, only in Japanese).
https://wrongwrong163377.hatenablog.com/entry/2025/07/05/175237
Also,
kotlin-modulehas lower deserialization performance thankogera, so the amount of improvement seen from the benchmark is lower than that ofkogera.Throughput
A ratio greater than 1 indicates improvement.
summarized at: https://docs.google.com/spreadsheets/d/1Qi60pL8SEXJA5dx-CJA_NrON-OahTyb9RM5jDUhVk94
The overall trend is read as an improvement.
Serialize
In all cases, the scores showed a speedup in all cases.
In particular, the most common case,
unbox.IntBenchmark.wrapped, showed more than a 2X speedup.Deserialize
In all cases, the scores showed a speedup in all cases.
In particular, the most common
byPrimaryConstructor.IntBenchmark.wrappedcase showed an improvement of about 3%.Single Shot
A ratio less than 1 indicates improvement.
summarized at: https://docs.google.com/spreadsheets/d/1zAr0zH6AVeOGdWz6WJd5-42jz3FrK1aHR-AmIiVNaaY
Since the amount of initialization processing is increasing, the overall trend can be read as degradation.
However, the amount of degradation itself is at worst about 6%, and in many cases it is less than 3%.
Serialize
Deserialize