From 90400757e850b88b9becba0d1cb9818f0ecd36af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= Date: Mon, 5 Oct 2026 10:38:41 +0200 Subject: [PATCH] Avoid Kotlin reflection for non-suspending return types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, KotlinDelegate.getReturnType() and getGenericReturnType() resolved the KFunction of every Kotlin method, which materializes the members of the declaring KClass and is costly at startup. Kotlin reflection is only needed to resolve the return type of suspending functions, so both methods now only use it when KotlinDetector.isSuspendingFunction() returns true. Closes gh-37300 Signed-off-by: Sébastien Deleuze --- .../springframework/core/MethodParameter.java | 38 ++++++++++--------- .../core/MethodParameterKotlinTests.kt | 32 ++++++++++++++++ 2 files changed, 53 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 b730f042d5b..2ea648b2d5e 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 class MethodParameter { * 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 @@ public class MethodParameter { * 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 d63dae5ae5d..c6f580cab2d 100644 --- a/spring-core/src/test/kotlin/org/springframework/core/MethodParameterKotlinTests.kt +++ b/spring-core/src/test/kotlin/org/springframework/core/MethodParameterKotlinTests.kt @@ -18,6 +18,8 @@ package org.springframework.core import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test +import org.mockito.Mockito.mockStatic +import org.mockito.stubbing.Answer import java.lang.reflect.Method import java.lang.reflect.TypeVariable import kotlin.coroutines.Continuation @@ -114,6 +116,30 @@ 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 return type does not use Kotlin reflection`() { + val methodParameter = returnMethodParameter("regularFun") + mockStatic(Class.forName("kotlin.reflect.jvm.ReflectJvmMapping"), Answer { + throw AssertionError("Kotlin reflection should not be used for regular functions") + }).use { + assertThat(methodParameter.parameterType).isEqualTo(Producer::class.java) + assertThat(methodParameter.genericParameterType.typeName) + .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 +212,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