From 8e8b1528185a054c81d8223f3ef01eab37aa8e22 Mon Sep 17 00:00:00 2001 From: Greg Taube Date: Sat, 19 Sep 2026 07:08:05 +0200 Subject: [PATCH] Avoid Kotlin reflection in MethodParameter for non-suspending functions 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 --- .../springframework/core/MethodParameter.java | 38 ++++++++++--------- .../core/MethodParameterKotlinTests.kt | 19 ++++++++++ 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/MethodParameter.java b/spring-core/src/main/java/org/springframework/core/MethodParameter.java index b730f042d5be..2ea648b2d5e4 100644 --- a/spring-core/src/main/java/org/springframework/core/MethodParameter.java +++ b/spring-core/src/main/java/org/springframework/core/MethodParameter.java @@ -972,14 +972,16 @@ public static boolean isOptional(MethodParameter param) { * functions via Kotlin reflection. */ private static Type getGenericReturnType(Method method) { - try { - KFunction function = ReflectJvmMapping.getKotlinFunction(method); - if (function != null && function.isSuspend()) { - return ReflectJvmMapping.getJavaType(function.getReturnType()); + if (KotlinDetector.isSuspendingFunction(method)) { + try { + KFunction function = ReflectJvmMapping.getKotlinFunction(method); + if (function != null && function.isSuspend()) { + return ReflectJvmMapping.getJavaType(function.getReturnType()); + } + } + catch (UnsupportedOperationException ex) { + // probably a synthetic class - let's use java reflection instead } - } - catch (UnsupportedOperationException ex) { - // probably a synthetic class - let's use java reflection instead } return method.getGenericReturnType(); } @@ -989,18 +991,20 @@ private static Type getGenericReturnType(Method method) { * functions via Kotlin reflection. */ private static Class getReturnType(Method method) { - try { - KFunction function = ReflectJvmMapping.getKotlinFunction(method); - if (function != null && function.isSuspend()) { - Type paramType = ReflectJvmMapping.getJavaType(function.getReturnType()); - if (paramType == Unit.class) { - paramType = void.class; + if (KotlinDetector.isSuspendingFunction(method)) { + try { + KFunction function = ReflectJvmMapping.getKotlinFunction(method); + if (function != null && function.isSuspend()) { + Type paramType = ReflectJvmMapping.getJavaType(function.getReturnType()); + if (paramType == Unit.class) { + paramType = void.class; + } + return ResolvableType.forType(paramType).resolve(method.getReturnType()); } - return ResolvableType.forType(paramType).resolve(method.getReturnType()); } - } - catch (UnsupportedOperationException ex) { - // probably a synthetic class - let's use java reflection instead + catch (UnsupportedOperationException ex) { + // probably a synthetic class - let's use java reflection instead + } } return method.getReturnType(); } diff --git a/spring-core/src/test/kotlin/org/springframework/core/MethodParameterKotlinTests.kt b/spring-core/src/test/kotlin/org/springframework/core/MethodParameterKotlinTests.kt index d63dae5ae5d5..c4e9216ab410 100644 --- a/spring-core/src/test/kotlin/org/springframework/core/MethodParameterKotlinTests.kt +++ b/spring-core/src/test/kotlin/org/springframework/core/MethodParameterKotlinTests.kt @@ -31,6 +31,7 @@ import kotlin.reflect.jvm.javaMethod * @author Sebastien Deleuze * @author Juergen Hoeller * @author Konrad Kaminski + * @author Greg Taube */ class MethodParameterKotlinTests { @@ -114,6 +115,18 @@ class MethodParameterKotlinTests { assertThat(returnGenericParameterType("suspendFun8")).isEqualTo(Any::class.java) } + @Test + fun `Regular function return type`() { + assertThat(returnParameterType("regularFun")).isEqualTo(Producer::class.java) + assertThat(returnGenericParameterTypeName("regularFun")).isEqualTo("org.springframework.core.Producer") + } + + @Test + fun `Regular function with Continuation parameter return type`() { + assertThat(returnParameterType("regularFunWithContinuation")).isEqualTo(Producer::class.java) + assertThat(returnGenericParameterTypeName("regularFunWithContinuation")).isEqualTo("org.springframework.core.Producer") + } + @Test fun `Parameter name for regular function`() { val methodParameter = returnMethodParameter("nullable", 0) @@ -186,6 +199,12 @@ class MethodParameterKotlinTests { @Suppress("unused", "unused_parameter") suspend fun suspendFun8(p1: String): Any? = TODO() + + @Suppress("unused", "unused_parameter") + fun regularFun(p1: String): Producer = TODO() + + @Suppress("unused", "unused_parameter") + fun regularFunWithContinuation(p1: String, continuation: Continuation): Producer = TODO() } interface Producer