Affects: 5.2.0.RC1
when using Kotlin suspend function and Validated annotation, like this.
package example
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@Validated
@RestController
class Foo {
@RequestMapping("/foo")
suspend fun foo() = ResponseEntity("foo", HttpStatus.OK)
}
occured exception.
java.lang.ArrayIndexOutOfBoundsException: 0
at java.util.Arrays$ArrayList.get(Arrays.java:3841) ~[na:1.8.0_102]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
|_ checkpoint ⇢ Handler example.Foo#foo(Continuation) [DispatcherHandler]
|_ checkpoint ⇢ org.springframework.security.web.server.authorization.AuthorizationWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.web.server.authorization.ExceptionTranslationWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.web.server.authentication.logout.LogoutWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.web.server.savedrequest.ServerRequestCacheWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.web.server.context.SecurityContextServerWebExchangeWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.web.server.context.ReactorContextWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.web.server.header.HttpHeaderWriterWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.config.web.server.ServerHttpSecurity$ServerWebExchangeReactorContextWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.web.server.WebFilterChainProxy [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.cloud.sleuth.instrument.web.TraceWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ HTTP GET "/foo" [ExceptionHandlingWebHandler]
Stack trace:
at java.util.Arrays$ArrayList.get(Arrays.java:3841) ~[na:1.8.0_102]
at org.hibernate.validator.internal.metadata.aggregated.ParameterMetaData$Builder.build(ParameterMetaData.java:169) ~[hibernate-validator-6.0.17.Final.jar:6.0.17.Final]
at org.hibern ate.validator.internal.metadata.aggregated.ExecutableMetaData$Builder.findParameterMetaData(ExecutableMetaData.java:435) ~[hibernate-validator-6.0.17.Final.jar:6.0.17.Final]
at org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData$Builder.build(ExecutableMetaData.java:388) ~[hibernate-validator-6.0.17.Final.jar:6.0.17.Final]
at org.hibernate.validator.internal.metadata.aggregated.BeanMetaDataImpl$BuilderDelegate.build(BeanMetaDataImpl.java:788) ~[hibernate-validator-6.0.17.Final.jar:6.0.17.Final]
at org.hibernate.validator.internal.metadata.aggregated.BeanMetaDataImpl$BeanMetaDataBuilder.build(BeanMetaDataImpl.java:648) ~[hibernate-validator-6.0.17.Final.jar:6.0.17.Final]
at org.hibernate.validator.internal.metadata.BeanMetaDataManager.createBeanMetaData(BeanMetaDataManager.java:204) ~[hibernate-validator-6.0.17.Final.jar:6.0.17.Final]
at org.hibernate.validator.internal.metadata.BeanMetaDataManager.getBeanMetaData(BeanMetaDataManager.java:166) ~[hibernate-validator-6.0.17.Final.jar:6.0.17.Final]
at org.hibernate.validator.internal.engine.ValidatorImpl.validateParameters(ValidatorImpl.java:265) ~[hibernate-validator-6.0.17.Final.jar:6.0.17.Final]
at org.hibernate.validator.internal.engine.ValidatorImpl.validateParameters(ValidatorImpl.java:233) ~[hibernate-validator-6.0.17.Final.jar:6.0.17.Final]
at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:104) ~[spring-context-5.2.0.RC1.jar:5.2.0.RC1]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.2.0.RC1.jar:5.2.0.RC1]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689) ~[spring-aop-5.2.0.RC1.jar:5.2.0.RC1]
...
Comment From: sdeleuze
@Validated
is indeed not yet Coroutines compliant, we need to fix that by using Coroutines aware methods to discover method parameters.
Comment From: sdeleuze
I am not entirely sure what we should do here. The issue comes from MethodValidationInterceptor
which is obviously not Coroutines aware, but JSR-303 is designed to deal with Java reflection API not Kotlin one. We could maybe pass a fake parameter value for the Continuation
one, but there are other issues like how to support annotation on return values, the fact that WebMvc is not supported yet, etc.
Given those uncertainties, it seems more reasonable to postpone this issue for 5.3.
Comment From: kostya05983
Hello, @sdeleuze , any updates? Today, I think about another annotation something like KValidated as temporary solution, For example you can add new annotation KValidated which should be used only in kotlin. And after add KMethodValidationInterceptor with similary behaviour as MethodValidationInterceptor, what do you think about this? And can you describe the problem with solution please?. Why we can't just use CoroutinesUtils.invokeSuspendingFunction(method, getBean(), args); as it is used in InvocableHandlerMethod?
Comment From: sdeleuze
@kostya05983 Even after adding support for Coroutines via CoroutinesUtils.invokeSuspendingFunction
there is an error at Hibernate Validator level, so I suggest you or somebody raises an issue on Hibernate Validator side for such support. See the related stack trace bellow:
at java.base/java.util.Arrays$ArrayList.get(Arrays.java:4351)
at org.hibernate.validator.internal.properties.javabean.JavaBeanExecutable.getParameterName(JavaBeanExecutable.java:86)
at org.hibernate.validator.internal.metadata.aggregated.ParameterMetaData$Builder.build(ParameterMetaData.java:165)
at org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData$Builder.findParameterMetaData(ExecutableMetaData.java:436)
at org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData$Builder.build(ExecutableMetaData.java:391)
at org.hibernate.validator.internal.metadata.aggregated.BeanMetaDataBuilder$BuilderDelegate.build(BeanMetaDataBuilder.java:260)
at org.hibernate.validator.internal.metadata.aggregated.BeanMetaDataBuilder.build(BeanMetaDataBuilder.java:133)
at org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl.createBeanMetaData(BeanMetaDataManagerImpl.java:206)
at org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl.getBeanMetaData(BeanMetaDataManagerImpl.java:165)
at org.hibernate.validator.internal.engine.ValidatorImpl.validateParameters(ValidatorImpl.java:267)
at org.hibernate.validator.internal.engine.ValidatorImpl.validateParameters(ValidatorImpl.java:235)
at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:110)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:691)
at com.example.FooCoroutinesController$$EnhancerBySpringCGLIB$$1b76b143.find(<generated>)
22462 fix should be all what is needed on Spring side.
Comment From: pschichtel
For reference, this is the hibernate issue: https://hibernate.atlassian.net/browse/HV-1638
Comment From: pschichtel
I've build a workaround for this issue: https://gist.github.com/pschichtel/830b7943ea43b7cb58cadd984b54b903
Comment From: vladimirfx
Hit by this while trying Spring GraphQL with validation in the classpath. This makes it impossible to call suspended @QueryMapping
methods.
Hibernate Validator issue is prioritized as Minor and unlikely to be fixed soon. Can validation be disabled at HandlerMethod level OR concrete method level?
Comment From: hantsy
I've build a workaround for this issue: https://gist.github.com/pschichtel/830b7943ea43b7cb58cadd984b54b903
Hope this long-awaited fix will be included in Spring although it was a problem from Hibernate Validator.
Comment From: hantsy
I created a sample project based on the solution provided by @pschichtel .
Comment From: ghost
https://gist.github.com/pschichtel/830b7943ea43b7cb58cadd984b54b903
just added your workaround to my project and got around the arrayindexoutofbounds with hibernate-validator enabled. The project was using openapi generator to create a reactive kotlin api based on webflux. with reactive=true and useBeanValidation=true every query failed with arrayindexoutofbounds
Comment From: cjdxhjj
@sdeleuze @sbrannen @bclozel my issure has discover a way to solve that problem https://github.com/spring-projects/spring-framework/issues/29793
Comment From: sdeleuze
Indeed #29566 that we made for unrelated reasons may allow us to solve this issue. i am not sure #29840 is the way I would implement it, but that provide a good basis for testing. I will have a deeper look and send my feedback.
Comment From: cjdxhjj
@sdeleuze thanks for your replay
Comment From: sdeleuze
Based on a quick test, it seems just removing the usage of KotlinReflectionParameterNameDiscoverer
in LocalValidatorFactoryBean
combined with the changes done in #29566 fix parameter validation with Coroutines.
Could people interested in getting that fix test my changes on https://github.com/sdeleuze/spring-framework/commit/gh-23499 and provide a feedback here?
Comment From: cjdxhjj
i don't think hibernate validation will compatible with the kotlin suspend function. i have read some replay from hibernate validation
https://hibernate.atlassian.net/browse/HV-1638 https://hibernate.atlassian.net/browse/HV-1796
Comment From: cjdxhjj
@sdeleuze
it may be solved by jetbrain remove that parameter
Comment From: sdeleuze
On Spring side, I have the feeling that KotlinReflectionParameterNameDiscoverer
in LocalValidatorFactoryBean
does more harm than good. Coroutines use case seems totally broken with it, works for typical use case (unless I get different feedback but that's what my quick test shown).
Some Kotlin use cases may still be broken until Kotlin team move forward on https://youtrack.jetbrains.com/issue/KT-40857, but I could ask them to move forward on that issue.
Could you please test my branch and let me know how it goes for typical use case?
Comment From: cjdxhjj
i will have a test
Comment From: sdeleuze
@cjdxhjj Any chance you could test and provide a feedback?
Comment From: cjdxhjj
@sdeleuze i'm sorry for the slow response, i'm on holidy, i would try it as soon as possible.
Comment From: cjdxhjj
@sdeleuze i have just do a simple test with
that works.
Comment From: sdeleuze
Bean validation on suspending function parameters should be fixed as of Spring Framework 6.0.5, I don't think the fix is doable easily on 5.3.x so I won't backport it. Validation of suspending function return values remains unsupported as Hibernate Validator is not Coroutines aware, but I think parameter validation was the most critical need.
Comment From: cjdxhjj
@sdeleuze thanks a lot
Comment From: Michal-Kucera
Hello! I'm not sure this is a related issue, though, let's see if we should open a new ticket.
After upgrading from Spring Boot 3.0.2 to 3.0.4 validations on GraphQL controller parameters work as expected, but I'm now having a problem with custom ConstraintValidators which are not triggered at all.
Please see a sample code:
@Controller
@Validated
class CompanyController {
@PreAuthorize("hasRole('ROLE_COMPANY_EDIT')")
@MutationMapping("createCompany")
suspend fun createCompany(
@Argument("input")
@Valid
companyInput: CompanyInput
): CompanyResponse {
// TODO..
}
}
@CompanyNameUniquePerWhitelabelId
data class CompanyInput(
val companyType: CompanyType,
val name: String,
@IsIso2Country val registeredAddressCountryCode: Int
)
@MustBeDocumented
@Constraint(validatedBy = [CompanyNameUniquePerWhitelabelIdValidator::class])
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class CompanyNameUniquePerWhitelabelId(
val message: String = "This company name already exists for Provided whitelabel.",
val groups: Array<KClass<*>> = [],
val payload: Array<KClass<out Any>> = []
)
class CompanyNameUniquePerWhitelabelIdValidator : ConstraintValidator<CompanyNameUniquePerWhitelabelId, CompanyInput> {
override fun isValid(companyInput: CompanyInput?, context: ConstraintValidatorContext?): Boolean {
// TODO
}
}
@MustBeDocumented
@Constraint(validatedBy = [Iso2CountryValidator::class])
@Target(AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
annotation class IsIso2Country(
val message: String = "Wrong country code",
val groups: Array<KClass<*>> = [],
val payload: Array<KClass<out Any>> = []
)
class Iso2CountryValidator : ConstraintValidator<IsIso2Country, Int?> {
override fun isValid(value: Int?, context: ConstraintValidatorContext?): Boolean {
// TODO
}
}
In my case, none of Iso2CountryValidator#isValid
and CompanyNameUniquePerWhitelabelIdValidator#isValid
is triggered.
Any thoughts?
Thanks! Michal
Comment From: koenpunt
@Michal-Kucera this might be related to this issue: https://github.com/spring-projects/spring-graphql/issues/624 which was fixed in this commit https://github.com/spring-projects/spring-graphql/commit/581b1108f21101a09e799489b54b5a2888c3e62c, and will be released with Spring GraphQL 1.1.3.
Comment From: Michal-Kucera
Thanks for your fast response @koenpunt! Next time I should perhaps first look into graphql issues :)
Comment From: rishiraj88
Nice issue request. Thanks, @Michal-Kucera