Skip to content

Avoid Kotlin reflection in MethodParameter for non-suspending functions #37300

Description

@gregjotau

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions