Skip to content

Regression: nullable Kotlin inline value class @RequestParam / @PathVariable in suspend functions fails with IllegalArgumentException in 7.0.7 #36854

@vpelikh

Description

@vpelikh

Description
After upgrading from Spring Framework 7.0.6 to 7.0.7, Kotlin inline value classes declared as nullable parameters (TestValueClass?) inside suspend controller methods cause an IllegalArgumentException. The error is:

java.lang.IllegalArgumentException: object of type java.lang.String is not an instance of ValueClassTestController$TestValueClass

This happens both for @RequestParam and @PathVariable binding.
In 7.0.6 the same code worked correctly; the value class was properly instantiated from the string value.
Interesting note: if the parameter is changed from nullable (TestValueClass?) to non-nullable (TestValueClass), the binding succeeds again in 7.0.7.

Steps to Reproduce

  1. Create a Spring WebFlux controller with a suspend function that has a nullable inline value class parameter annotated with @RequestParam or @PathVariable.
  2. Use WebTestClient to send a request with a valid string value for that parameter.
  3. Run under Spring Framework 7.0.6 → test passes.
  4. Run under 7.0.7 → test fails with IllegalArgumentException.

Minimal reproduction

import org.junit.jupiter.api.Test
import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.test.web.reactive.server.expectBody
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController

@RestController
class ValueClassTestController {
    @GetMapping(path = ["/test"])
    suspend fun test1(@RequestParam param: TestValueClass?): String? {
        return param?.value
    }

    @GetMapping(path = ["/test/{param}"])
    suspend fun test2(@PathVariable param: TestValueClass?): String? {
        return param?.value
    }

    @JvmInline
    value class TestValueClass(val value: String)
}

class ValueClassControllerTest {
    private val webTestClient = WebTestClient.bindToController(ValueClassTestController()).build()

    @Test
    fun `test RequestParam should return correct value`() {
        webTestClient.get().uri("/test?param=123")
            .exchange()
            .expectStatus().isOk
            .expectBody<String>().isEqualTo("123")
    }

    @Test
    fun `test PathVariable should return correct value`() {
        webTestClient.get().uri("/test/456")
            .exchange()
            .expectStatus().isOk
            .expectBody<String>().isEqualTo("456")
    }
}

Expected Behavior
Both tests pass; the client receives "123" and "456" respectively.

Actual Behavior (7.0.7)
Both tests fail with a 500 INTERNAL SERVER ERROR. The root cause is:

java.lang.IllegalArgumentException: object of type java.lang.String is not an instance of ValueClassTestController$TestValueClass
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.checkReceiver(DirectMethodHandleAccessor.java:199)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:100)
	...
	at kotlin.reflect.jvm.internal.calls.ValueClassAwareCaller.call(ValueClassAwareCaller.kt:137)
	at kotlin.reflect.jvm.internal.ReflectKCallableKt.callDefaultMethod(ReflectKCallable.kt:153)
	at kotlin.reflect.full.KCallables.callSuspendBy(KCallables.kt:82)
	at org.springframework.core.CoroutinesUtils.lambda$invokeSuspendingFunction$1(CoroutinesUtils.java:148)

The stack trace reveals that the string value provided by the framework is passed directly to the Kotlin reflection call, but the reflection layer expects an already‑boxed value class instance. For nullable parameters, the framework incorrectly hands over the raw String instead of wrapping it in the value class (or leaving it as null).
When the parameter is changed to TestValueClass (non-nullable), the problem disappears – the binding works again.

Workaround
Avoid nullable inline value class parameters in suspend functions.

Environment

  • Spring Framework version: 7.0.7 (regressed from 7.0.6)
  • Kotlin version: 2.3.21
  • Java version: 25
  • Web stack: Spring WebFlux
  • Build tool: Gradle

Additional Context

Metadata

Metadata

Assignees

Labels

status: duplicateA duplicate of another issuetheme: kotlinAn issue related to Kotlin support

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions