ReflectJvmMapping.getKotlinFunction returns null for Kotlin properties and must be handled properly (see #31856).

I hit another erroneous usage here:

2025-01-19T08:43:01.882+01:00  INFO 8982 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2025-01-19T08:43:01.882+01:00  INFO 8982 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2025-01-19T08:43:01.883+01:00  INFO 8982 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms
2025-01-19T08:43:01.935+01:00 ERROR 8982 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.IllegalArgumentException: Kotlin function must not be null] with root cause

java.lang.IllegalArgumentException: Kotlin function must not be null
        at org.springframework.util.Assert.notNull(Assert.java:181)
        at org.springframework.http.converter.AbstractKotlinSerializationHttpMessageConverter.serializer(AbstractKotlinSerializationHttpMessageConverter.java:153)
        at org.springframework.http.converter.AbstractKotlinSerializationHttpMessageConverter.canWrite(AbstractKotlinSerializationHttpMessageConverter.java:96)
        at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:328)
        at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:208)
        at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:78)
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:136)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891)
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1088)
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:978)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
        at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903)
        at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564)
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
        at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
        at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
        at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344)
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:397)
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:905)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741)
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
        at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190)
        at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
        at java.base/java.lang.Thread.run(Thread.java:1575)

More Assert.notNull on the value returned from ReflectJvmMapping.getKotlinFunction can be found in spring-framework sources.

Comment From: bclozel

Unfortunately, we can't triage this issue right now. You have not given us much to work with.

Is this a regression, meaning that the sample in the linked issue doesn't work anymore as it did after the initial fix?

Which Spring and Kotlin versions are you using?

How can we reproduce the problem?

Comment From: wolfseifert

Sorry for the shortness of my issue description. But if you assign this issue to someone like @sdeleuze (who fixed #31856) he will know what to do: grep the springframework for ReflectJvmMapping.getKotlinFunction and check if all returned null are handled correctly.

Comment From: snicoll

@wolfseifert that's not an acceptable answer: it's totally fair that one of us asks for additional details. If you want us to consider the report, make it actionable as Brian asked.

Comment From: wolfseifert

Here is a small reproducer: spring-boot.zip

  • Unzip spring-boot.zip
  • Set JAVA_HOME to /usr/lib/jvm/java-17-openjdk, /usr/lib/jvm/java-21-openjdk or /usr/lib/jvm/java-23-openjdk
  • $ gradlew bootRun
  • $ gradlew client

The IllegalArgumentException: Kotlin function must not be null from above appears in the spring-boot console.

Versions: - OpenJDK 17, 21 or 23 - Gradle 8.12 - Kotlin 2.1.0 - Spring-Boot 3.4.1

OS: Linux or Windows

Comment From: sdeleuze

Fixed with the limitation that the type used for Kotlin properties is created from the Java type, not the Kotlin one, since kotlin-reflect does not provide a direct match from the Java method to the Kotlin property.

As mentioned in https://github.com/spring-projects/spring-framework/issues/31856#issuecomment-1862606610, the main reason we support this use case is to avoid unnecessary Kotlin checks at RequestMappingHandlerMapping level, and it was introduced at a time where non suspending function were invoked with Java reflection, not Kotlin one like in 6.2+.

Kotlin properties are supported in a best effort fashion, this is not something I recommend or plan to document explicitly. If more side effects are found, I may decide to remove the support in a future major release, but for now let's try to keep this pragramatic status quo.