I was able to reproduce the issue with one of spring-boots own examples. Following Getting Started > Building an Application with Spring Boot:

$ git clone https://github.com/spring-guides/gs-spring-boot.git
$ cd gs-spring-boot/initial

Rename HelloController.java to HelloController.kt and change it into:

package com.example.springboot

import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController

@RestController
class HelloController {
  val index: String
    @GetMapping("/")
    get() = "Greetings from Spring Boot!"
}

Change build.gradle into:

plugins {
  id 'org.springframework.boot' version '3.2.0'
  id 'io.spring.dependency-management' version '1.1.4'
  id 'org.jetbrains.kotlin.jvm' version '1.9.21'
}

repositories {
  mavenCentral()
}

dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-web'
  runtimeOnly 'org.jetbrains.kotlin:kotlin-reflect:1.9.21'
}

Then run it with ./gradlew bootRun:

2023-12-17T15:43:49.074+01:00 ERROR 27483 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.NullPointerException] with root cause

java.lang.NullPointerException: null
  at java.base/java.util.Objects.requireNonNull(Objects.java:209) ~[na:na]
  at org.springframework.web.method.support.InvocableHandlerMethod$KotlinDelegate.invokeFunction(InvocableHandlerMethod.java:302) ~[spring-web-6.1.1.jar:6.1.1]
  at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:251) ~[spring-web-6.1.1.jar:6.1.1]
  at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:182) ~[spring-web-6.1.1.jar:6.1.1]
  at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) ~[spring-webmvc-6.1.1.jar:6.1.1]
  at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:917) ~[spring-webmvc-6.1.1.jar:6.1.1]
  at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:829) ~[spring-webmvc-6.1.1.jar:6.1.1]
  at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-6.1.1.jar:6.1.1]
  at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) ~[spring-webmvc-6.1.1.jar:6.1.1]
  at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) ~[spring-webmvc-6.1.1.jar:6.1.1]
  at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) ~[spring-webmvc-6.1.1.jar:6.1.1]
  at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) ~[spring-webmvc-6.1.1.jar:6.1.1]
  at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564) ~[tomcat-embed-core-10.1.16.jar:6.0]
  at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) ~[spring-webmvc-6.1.1.jar:6.1.1]
  at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) ~[tomcat-embed-core-10.1.16.jar:6.0]
  at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
  at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
  at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) ~[tomcat-embed-websocket-10.1.16.jar:10.1.16]
  at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
  at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
  at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.1.1.jar:6.1.1]
  at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.1.jar:6.1.1]
  at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
  at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
  at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-6.1.1.jar:6.1.1]
  at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.1.jar:6.1.1]
  at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
  at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
  at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-6.1.1.jar:6.1.1]
  at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.1.jar:6.1.1]
  at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
  at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
  at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
  at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
  at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
  at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
  at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
  at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
  at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:340) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
  at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:391) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
  at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
  at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:896) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
  at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1744) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
  at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
  at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
  at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
  at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
  at java.base/java.lang.Thread.run(Thread.java:840) ~[na:na]

curl http://localhost:8080:

{"timestamp":"2023-12-17T14:44:52.410+00:00","status":500,"error":"Internal Server Error","path":"/"}

Removing runtimeOnly 'org.jetbrains.kotlin:kotlin-reflect:1.9.21' from build.gradle resolves the issue:

$ ./gradlew bootRun
$ curl http://localhost:8080
Greetings from Spring Boot!

So the problem appears only with kotlin-reflect on the (runtime) classpath. This is queried in org.springframework.web.method.support.InvocableHandlerMethod:246 and leads to an exception in InvocableHandlerMethod:302 if the case. The JVM method getIndex() is not found in the list of Kotlin methods (equals() etc.).

Comment From: philwebb

@bclozel could you please transfer this one to the Framework issue tracker.

Comment From: snicoll

Then run it:

Thanks for the detailed explanation but there's no added value in us replicating that sample from the steps you've described. Our time would be better spent looking at the final example as you see it and understand what could be the issue.

Can you please first upgrade your sample to use Spring Framework 6.1.2-SNAPSHOT as we've fixed numerous things in that area (you can also upgrade the sample to Spring Boot 3.2.1-SNAPSHOT). If the issue persists, please attach the sample here as a zip or push the updated code to a separate GitHub repository.

Comment From: wolfseifert

I do not understand your problem as the "final example" is in the issue description - just copy and paste!

Anyway, here are the zips spring-boot-3.2.0.zip spring-boot-3.2.1-SNAPSHOT.zip

(Same problem with 3.2.1-SNAPSHOT)

Comment From: snicoll

I do not understand your problem as the "final example" is in the issue description - just copy and paste!

I don't have a problem. You're making it sound way simpler than it often is. If you have the repro as a small sample, describing the steps to get to there is really a bad idea as a step might be missing or misleading and we'd end up in a back and forth trying to figure out the missing bit, and that's frustrating for everybody.

As I've already explained, I suppose we can both agree you prefer us investigating the actual problem rather than spending time rebuilding the pieces to reproduce it.

Comment From: snicoll

I am not a kotlin expert but what is the purpose of that val index there?

val index: String
    @GetMapping("/")
    get() = "Greetings from Spring Boot!"

get() isn't a function of HelloController. Of course we shouldn't end up in an NPE but I suspect the method shouldn't have been detected in the first place.

Comment From: wolfseifert

Kotlin's

val index: String
  @GetMapping("/")
  get() = "Greetings from Spring Boot!"

produces something like Java's

  @GetMapping("/")
  String getIndex() { 
    return "Greetings from Spring Boot!";
  }

See the last paragraph of my initial issue description for an error analysis. I think a Kotlin expert is needed here.

Comment From: wolfseifert

Here is a patch for the issue (for spring-framework main): patch.txt

Comment From: sdeleuze

There was 2 main approaches possible to solve this issue: - Filter out Kotlin property accessors at RequestMappingHandlerMapping level to skip them - Add support for such use case in InvocableHandlerMethod

I chose the later because conceptually, those Kotlin property accessors translate into plain Java methods that we are unable to distinguish from regular Java methods unless Kotlin reflection is used. That also avoid to introduce an unnecessary performance overhead.

I also chose to not introduce a dependency on Kotlin reflection in KotlinDetector as kotlin-reflect is not a mandatory dependency of Spring, and as the checks performed at KotlinDetector are expected to be fast and only rely on JVM bytecode. The check in KotlinDelegate is also more efficient as the return value of ReflectJvmMapping.getKotlinFunction can be reused.