Closed
Description
I've encountered a bug in Spring Framework version 6.1.0 related to Kotlin value classes in web endpoints. The issue arises when a Kotlin value class
is used as a path variable in a Spring Boot controller. This results in a failure to correctly handle the value type, causing runtime exceptions.
Environment
Spring Framework Version: 6.1.0 (with spring-boot 3.2.0)
Kotlin Version: 1.9.20
JVM Version: 17
Steps to reproduce
- Define a Spring Boot controller with two endpoints, one accepting a Kotlin data class as a path variable and another accepting a Kotlin value class.
- Create tests for these endpoints using SpringBootTest.
- Observe that the endpoint with the Kotlin data class functions correctly, while the one with the kotlin value class fails.
Expected behavior
Both endpoints should accept their respective path variables without any issue
Actual Behavior
The endpoint with the Kotlin value class as a path variable fails at runtime with the error message "object is not an instance of declaring class".
Minimal example
@RestController
@RequestMapping(value = ["/exhibit"])
class KotlinCallByBugController {
@GetMapping("/working-id/{id}")
fun works(
@PathVariable id: WorkingId
) = Unit
@GetMapping("/value-id/{id}")
fun broken(
@PathVariable id: SomeId
) = Unit
}
@JvmInline // "Value classes without @JvmInline annotation are not supported yet"
value class SomeId(val s: String)
// data classes work just fine
data class WorkingId(val s: String)
@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = [MyApplication::class])
class KotlinCallByBugControllerTest {
@LocalServerPort
protected val port = 0
@Test
fun `this works - call endpoint with path variable as data class`() {
val client = HttpClients.createDefault()
client.execute(HttpGet("http://localhost:$port/exhibit/working-id/123")) { response ->
assertThat(response.code).isEqualTo(200)
}
}
@Test
fun `this does not work - call endpoint with path variable as value class`() {
// breaks: "object is not an instance of declaring class"
val client = HttpClients.createDefault()
client.execute(HttpGet("http://localhost:$port/exhibit/value-id/123")) { response ->
assertThat(response.code).isEqualTo(200)
}
}
}