-
Notifications
You must be signed in to change notification settings - Fork 38.6k
Description
Affects: Spring Framework 6.1.4, Spring Boot 3.2.3
This is related to support added in #27345 for value classes. This is because it tries to box an already boxed value class.
Take for example this handler method:
@JvmInline value class SomeId(val int: Int)
@GetMapping("/path")
suspend fun handle(
@RequestParam nonNull: SomeId,
@RequestParam nullable: SomeId?,
)
Calling /path?nonNull=1&nullable=2
will fail because the type of the nullable
method parameter is SomeId?
and Spring cannot find a Converter
from String -> SomeId
.
If we instead add a custom converter from String -> SomeId
, the same endpoint will still fail because now the resolved argument has type SomeId
but invokeSuspendingFunction
wants to box it because its a value class.
My current workaround is to override invokeSuspendingFunction with my own variant that checks the arg[index] type first before boxing.
Adding excerpt from my own code comments:
Because of the way value classes work on the jvm, the runtime java reflection types of both parameters,
and hence the type that the spring handler method arguments have are Class<Int>
& Class<SomeId>
respectively.
These are the types used to [resolve arguments][HandlerMethodArgumentResolver.resolveArgument]
and
more importantly, to convert from request param, request header etc.
(In our case, since value classes are not handled by default, we configure a special [GenericConverter]
that will convert into a value class type from request params, headers etc.)
However, run time kotlin reflection types for both parameters are KClass<SomeId>
&& KClass<SomeId?>
and these are the types used in [CoroutinesUtils.invokeSuspendingFunction]
to determine whether
to box the converted argument or not.
This argument could be of wrapped type or value class type
depending on whether the declaration is nullable or not and boxing will fail on the latter!
This method is a kotlin port of [CoroutinesUtils.invokeSuspendingFunction]
that checks
if the instance type is the value class just before deciding whether to box or not