Conversation
MethodParameter resolves the return type of Kotlin methods through ReflectJvmMapping.getKotlinFunction(...) only to check whether the function is suspending. That lookup materializes all members of the declaring KClass via Kotlin reflection, for every method. A suspending function always compiles to a JVM method whose last parameter is kotlin.coroutines.Continuation, which KotlinDetector.isSuspendingFunction(...) already checks without Kotlin reflection. Use it as a guard so that regular Kotlin methods fall back to plain Java reflection immediately, while suspending functions keep the existing KFunction based return type resolution. Signed-off-by: Greg Taube <gregjotau@gmail.com>
gregjotau
force-pushed
the
avoid-kotlin-reflection-in-method-parameter
branch
from
September 19, 2026 05:34
636e894 to
8e8b152
Compare
Contributor
Author
|
Handing this over to the team: please feel free to take the change from here in whatever form suits you (team commit, different shape, or a different guard). We don't need authorship or ownership of the code; what we care about is the startup cost, so use it as you see fit. We have tested it thoroughly against reai.no, a large Spring Boot Kotlin accounting application, with the profiles and numbers in the description above. The signed-off commit stays reachable at |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
MethodParameter.getGenericParameterType()andgetParameterType()resolve the return type of a Kotlin method (parameter index-1) throughKotlinDelegate, which callsReflectJvmMapping.getKotlinFunction(method)only to checkKFunction.isSuspend(). That lookup materializes all members of the declaringKClassthrough Kotlin reflection (metadata deserialization plus a linear scan comparingjavaMethodper member) and is repeated for every method whose return type is resolved.A suspending function always compiles to a JVM method whose last parameter is
kotlin.coroutines.Continuation, whichKotlinDetector.isSuspendingFunction(Method)already checks without Kotlin reflection; the rest of the framework (AOP, caching, scheduling, messaging,ConstructorResolver) relies on that check. This change uses it as a guard inKotlinDelegate.getGenericReturnType(...)andgetReturnType(...), so regular Kotlin methods fall back toMethod.getGenericReturnType()/Method.getReturnType()immediately. Suspending functions keep the existingKFunctionbased resolution, and a non-suspending function that declares a trailingContinuationparameter explicitly is still verified throughKFunction.isSuspend(), so results are unchanged in every case.Measurements
Cold-JVM micro-benchmark that constructs
new MethodParameter(method, -1)and callsgetGenericParameterType()andgetParameterType()for the 1,418 non-synthetic methods of the 337 Kotlin repository interfaces of a large Spring Boot 4.2.0-SNAPSHOT / Framework 7.1.0-SNAPSHOT application (JDK 27, Apple M5 Max, 5 runs each):mainThe baseline number includes Kotlin reflection bootstrap, which the patched path avoids entirely; in an application that touches Kotlin reflection elsewhere the saving is correspondingly smaller. In a wall-clock startup profile of that application (async-profiler, 2 ms sampling, extracted Boot layout, 243 Spring Data JPA repositories),
MethodParameter$KotlinDelegate.getGenericReturnType/getReturnTypeaccounted for 124 of 8,694 main-thread samples (1.4 % of startup), all belowReflectJvmMapping.getKotlinFunction; with this change the frames no longer appear. Spring Data'sKotlinReflectionUtils.isSuspendhas the same pattern; I have proposed the equivalent guard there in spring-projects/spring-data-commons#3545.Verification
MethodParameterKotlinTestsgains two tests that pin the return type resolution of a regular function and of a regular function that declares aContinuationparameter explicitly; both pass onmainand on this branch../gradlew :spring-core:checkpasses on JDK 25, including the JDK 21 and JDK 24 test suites, checkstyle and architecture checks. Related to #21546.