Home > database >  Spring Boot 3 with CustomValidator and native Executable
Spring Boot 3 with CustomValidator and native Executable

Time:11-29

Hello Spring Boot Experts,

I'm trying to create a annotation custom validation, when i execute this in JVM Mode everything works as expected, but when i execute as native image i get this error

StackTrace

2022-11-28T17:09:40.312 01:00 ERROR 14056 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'info.novatec.validator.api.validators.ClaimNumberValidator': Failed to instantiate [info.novatec.validator.api.validators.ClaimNumberValidator]: No default constructor found] with root cause

java.lang.NoSuchMethodException: info.novatec.validator.api.validators.ClaimNumberValidator.<init>()
        at [email protected]/java.lang.Class.getConstructor0(DynamicHub.java:3585) ~[CustomValidatorExample:na]
        at [email protected]/java.lang.Class.getDeclaredConstructor(DynamicHub.java:2754) ~[CustomValidatorExample:na]
        at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:79) ~[CustomValidatorExample:6.0.2]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1300) ~[CustomValidatorExample:6.0.2]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1198) ~[CustomValidatorExample:6.0.2]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:561) ~[CustomValidatorExample:6.0.2]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:521) ~[CustomValidatorExample:6.0.2]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:321) ~[CustomValidatorExample:6.0.2]
        at org.springframework.validation.beanvalidation.SpringConstraintValidatorFactory.getInstance(SpringConstraintValidatorFactory.java:56) ~[na:na]
        at org.hibernate.validator.internal.engine.constraintvalidation.ClassBasedValidatorDescriptor.newInstance(ClassBasedValidatorDescriptor.java:84) ~[na:na]
        at org.hibernate.validator.internal.engine.constraintvalidation.AbstractConstraintValidatorManagerImpl.createAndInitializeValidator(AbstractConstraintValidatorManagerImpl.java:89) ~[CustomValidatorExample:8.0.0.Final]
        at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManagerImpl.getInitializedValidator(ConstraintValidatorManagerImpl.java:117) ~[na:na]
        at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.getInitializedConstraintValidator(ConstraintTree.java:136) ~[CustomValidatorExample:8.0.0.Final]
        at org.hibernate.validator.internal.engine.constraintvalidation.SimpleConstraintTree.validateConstraints(SimpleConstraintTree.java:58) ~[na:na]
        at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.validateConstraints(ConstraintTree.java:75) ~[CustomValidatorExample:8.0.0.Final]
        at org.hibernate.validator.internal.metadata.core.MetaConstraint.doValidateConstraint(MetaConstraint.java:130) ~[CustomValidatorExample:8.0.0.Final]
        at org.hibernate.validator.internal.metadata.core.MetaConstraint.validateConstraint(MetaConstraint.java:123) ~[CustomValidatorExample:8.0.0.Final]
        at org.hibernate.validator.internal.engine.ValidatorImpl.validateMetaConstraint(ValidatorImpl.java:555) ~[na:na]
        at org.hibernate.validator.internal.engine.ValidatorImpl.validateMetaConstraints(ValidatorImpl.java:537) ~[na:na]
        at org.hibernate.validator.internal.engine.ValidatorImpl.validateParametersForSingleGroup(ValidatorImpl.java:991) ~[na:na]
        at org.hibernate.validator.internal.engine.ValidatorImpl.validateParametersForGroup(ValidatorImpl.java:932) ~[na:na]
        at org.hibernate.validator.internal.engine.ValidatorImpl.validateParametersInContext(ValidatorImpl.java:863) ~[na:na]
        at org.hibernate.validator.internal.engine.ValidatorImpl.validateParameters(ValidatorImpl.java:283) ~[na:na]
        at org.hibernate.validator.internal.engine.ValidatorImpl.validateParameters(ValidatorImpl.java:235) ~[na:na]
        at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:121) ~[na:na]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[CustomValidatorExample:6.0.2]
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:752) ~[na:na]
        at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:703) ~[na:na]
        at info.novatec.validator.api.ClaimNumberController$$SpringCGLIB$$0.isClaimNumberValid(<generated>) ~[CustomValidatorExample:na]
        at [email protected]/java.lang.reflect.Method.invoke(Method.java:568) ~[CustomValidatorExample:na]
        at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:207) ~[CustomValidatorExample:6.0.2]
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:152) ~[CustomValidatorExample:6.0.2]
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) ~[CustomValidatorExample:6.0.2]
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884) ~[CustomValidatorExample:6.0.2]
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797) ~[CustomValidatorExample:6.0.2]
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[CustomValidatorExample:6.0.2]
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1080) ~[CustomValidatorExample:6.0.2]
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:973) ~[CustomValidatorExample:6.0.2]
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1003) ~[CustomValidatorExample:6.0.2]
        at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:895) ~[CustomValidatorExample:6.0.2]
        at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:705) ~[CustomValidatorExample:6.0]
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:880) ~[CustomValidatorExample:6.0.2]
        at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:814) ~[CustomValidatorExample:6.0]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:223) ~[na:na]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[na:na]
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[CustomValidatorExample:10.1.1]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[na:na]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[na:na]
        at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[CustomValidatorExample:6.0.2]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[CustomValidatorExample:6.0.2]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[na:na]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[na:na]
        at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[CustomValidatorExample:6.0.2]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[CustomValidatorExample:6.0.2]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[na:na]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[na:na]
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[CustomValidatorExample:6.0.2]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[CustomValidatorExample:6.0.2]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[na:na]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[na:na]
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) ~[na:na]
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[na:na]
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) ~[CustomValidatorExample:10.1.1]
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:119) ~[na:na]
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[CustomValidatorExample:10.1.1]
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[na:na]
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357) ~[na:na]
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:400) ~[na:na]
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[CustomValidatorExample:10.1.1]
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861) ~[na:na]
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1739) ~[na:na]
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[CustomValidatorExample:10.1.1]
        at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[na:na]
        at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[na:na]
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[na:na]
        at [email protected]/java.lang.Thread.run(Thread.java:833) ~[CustomValidatorExample:na]
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:775) ~[CustomValidatorExample:na]
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.posix.thread.PosixPlatformThreads.pthreadStartRoutine(PosixPlatformThreads.java:203) ~[na:na]

My Validator

import jakarta.validation.Constraint
import jakarta.validation.ConstraintValidator
import jakarta.validation.ConstraintValidatorContext
import jakarta.validation.Payload
import kotlin.annotation.AnnotationTarget.VALUE_PARAMETER
import kotlin.reflect.KClass

@Target(VALUE_PARAMETER)
@MustBeDocumented
@Constraint(validatedBy = [ClaimNumberValidator::class])
annotation class ClaimNumber(
    val message: String = "ClaimNumber must 11 chars long and start with 'KR'",
    val groups: Array<KClass<*>> = [],
    val payload: Array<KClass<out Payload>> = []
)

class ClaimNumberValidator: ConstraintValidator<ClaimNumber, String> {
    override fun isValid(value: String?, context: ConstraintValidatorContext?): Boolean {
        return value!=null && value.trim().length == 11 && value.trim().startsWith("KR")
    }
}

My Controller

@RestController
@RequestMapping("/claimNumber")
@Validated
class ClaimNumberController {

    @GetMapping("/{claimNumber}/isValid")
    fun isClaimNumberValid(
            @PathVariable
            @ClaimNumber
            claimNumber: String
    ): Boolean {
        return true
    }
}

The commplete example is here: CustomValidatorExample

I found this bug 1570 in project spring-native (which is now superseeded by spring boot 3). The problem is not the same but similar.

My Questions are:

  • Should I raise a bug report in spring boot 3 also?
  • Can I use TypeHints to get it working? If yes, How to do it?

CodePudding user response:

This will be addressed in Spring Framework. In the meantime, you can work around the problem by registering an additional reflection hint:

package info.novatec.validator

import info.novatec.validator.api.validators.ClaimNumberValidator
import org.springframework.aot.hint.MemberCategory
import org.springframework.aot.hint.RuntimeHints
import org.springframework.aot.hint.RuntimeHintsRegistrar
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.ImportRuntimeHints

@ImportRuntimeHints(ClaimNumberValidatorRuntimeHints::class)
@SpringBootApplication
class CustomValidatorExampleApplication

fun main(args: Array<String>) {
    runApplication<CustomValidatorExampleApplication>(*args)
}

class ClaimNumberValidatorRuntimeHints : RuntimeHintsRegistrar {
    override fun registerHints(hints: RuntimeHints, classLoader: ClassLoader?) {
        hints.reflection().registerType(ClaimNumberValidator::class.java, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)
    }
}

  • Related