MethodParameter.getGenericParameterType() and getParameterType() resolve the return type of a Kotlin method (parameter index -1) through KotlinDelegate, which calls ReflectJvmMapping.getKotlinFunction(method) only to check KFunction.isSuspend(). That lookup materializes all members of the declaring KClass through Kotlin reflection and is repeated for every method whose return type is resolved, including the many non-suspending ones.
A suspending function always compiles to a JVM method whose last parameter is kotlin.coroutines.Continuation, which KotlinDetector.isSuspendingFunction(Method) already checks without Kotlin reflection and which the rest of the framework (AOP, caching, scheduling, messaging, ConstructorResolver) relies on. Guarding KotlinDelegate.getGenericReturnType(...) and getReturnType(...) with it keeps results identical (suspending functions still go through the KFunction path; a non-suspending function with an explicit trailing Continuation parameter is still verified via KFunction.isSuspend()) while regular Kotlin methods fall back to Java reflection immediately.
Measurements on reai.no, a large Spring Boot Kotlin application (Boot 4.2.0-SNAPSHOT / Framework 7.1.0-SNAPSHOT, JDK 27, Apple M5 Max):
|
main |
With guard |
| Cold-JVM return type resolution for the 1,418 methods of 337 Kotlin repository interfaces, 5 runs |
1,584–2,766 ms |
57–150 ms |
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 / getReturnType accounted for 124 of 8,694 main-thread samples (1.4 % of startup), all below ReflectJvmMapping.getKotlinFunction; with the guard the frames no longer appear.
A signed-off implementation with two pinning tests in MethodParameterKotlinTests (green :spring-core:check on JDK 25 including the JDK 21/24 suites, CI green) is in #37299, whose fork I have since removed; the commit stays reachable at refs/pull/37299/head. We do not need authorship, please take it in whatever form suits the team. The equivalent guard for Spring Data Commons is tracked in spring-projects/spring-data-commons#3544. Related to #21546.
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 and is repeated for every method whose return type is resolved, including the many non-suspending ones.A suspending function always compiles to a JVM method whose last parameter is
kotlin.coroutines.Continuation, whichKotlinDetector.isSuspendingFunction(Method)already checks without Kotlin reflection and which the rest of the framework (AOP, caching, scheduling, messaging,ConstructorResolver) relies on. GuardingKotlinDelegate.getGenericReturnType(...)andgetReturnType(...)with it keeps results identical (suspending functions still go through theKFunctionpath; a non-suspending function with an explicit trailingContinuationparameter is still verified viaKFunction.isSuspend()) while regular Kotlin methods fall back to Java reflection immediately.Measurements on reai.no, a large Spring Boot Kotlin application (Boot 4.2.0-SNAPSHOT / Framework 7.1.0-SNAPSHOT, JDK 27, Apple M5 Max):
mainIn 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 the guard the frames no longer appear.A signed-off implementation with two pinning tests in
MethodParameterKotlinTests(green:spring-core:checkon JDK 25 including the JDK 21/24 suites, CI green) is in #37299, whose fork I have since removed; the commit stays reachable atrefs/pull/37299/head. We do not need authorship, please take it in whatever form suits the team. The equivalent guard for Spring Data Commons is tracked in spring-projects/spring-data-commons#3544. Related to #21546.