spring-native
spring-native copied to clipboard
Add reflection hints for custom validation annotations
Hello,
I'm trying to create a annotation custom validation, when i execute this in JAR file, work fine, but when i execute a native image i get this error
CustomAnnotation:
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = MaxPageSizeValidator.class)
public @interface MaxPageSizeValidation {
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
And the class:
@RequiredArgsConstructor
public class MaxPageSizeValidator implements ConstraintValidator<MaxPageSizeValidation, Integer> {
private final Internationalization internationalization;
@Override
public boolean isValid(Integer pageSize, ConstraintValidatorContext constraintValidatorContext) {
if (pageSize > 1000) {
throw new MyException(
Set.of(
ErrorResponse.valueOf(
String.valueOf(HttpStatus.UNPROCESSABLE_ENTITY.value()),
ErrorCode.INVALID_PARAMETER.name(),
internationalization.getMessage(constraintValidatorContext.getDefaultConstraintMessageTemplate())
)
)
);
}
return true;
}
}
My controller is like this:
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/endpoint")
public class MyController {
private final MyHandler myHandler;
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<MyResponse> findAll(
@RequestParam(value = "page", required = false, defaultValue = "1")
@Min(value = 1, message = "{page.must-be-greater-than-or-equal-1}")
Integer page,
@RequestParam(value = "page-size", required = false, defaultValue = "10")
@Min(value = 1, message = "{page-size.must-be-greater-than-0}")
@MaxPageSizeValidation(message = "{page-size.must-be-between-0-and-1000}")
Integer pageSize
) {
return ResponseEntity.ok().body(myHandler.findAll(page, pageSize));
}
}
And then when i call endpoint, the error:
javax.validation.ConstraintDefinitionException: HV000074: br.com.rj.systems.pocserviceproviders.external.validations.annotations.MaxPageSizeValidation contains Constraint annotation, but does not contain a message parameter.
at org.hibernate.validator.internal.metadata.core.ConstraintHelper.assertMessageParameterExists(ConstraintHelper.java:1057) ~[na:na]
at org.hibernate.validator.internal.metadata.core.ConstraintHelper.lambda$isConstraintAnnotation$3(ConstraintHelper.java:1003) ~[na:na]
at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1705) ~[na:na]
at org.hibernate.validator.internal.metadata.core.ConstraintHelper.isConstraintAnnotation(ConstraintHelper.java:1002) ~[na:na]
at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.findConstraintAnnotations(AnnotationMetaDataProvider.java:534) ~[na:na]
at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.findConstraints(AnnotationMetaDataProvider.java:479) ~[na:na]
at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.getParameterMetaData(AnnotationMetaDataProvider.java:412) ~[na:na]
at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.findExecutableMetaData(AnnotationMetaDataProvider.java:308) ~[na:na]
at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.getMetaData(AnnotationMetaDataProvider.java:292) ~[na:na]
at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.getMethodMetaData(AnnotationMetaDataProvider.java:279) ~[na:na]
at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.retrieveBeanConfiguration(AnnotationMetaDataProvider.java:130) ~[na:na]
at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.getBeanConfiguration(AnnotationMetaDataProvider.java:120) ~[na:na]
at org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl.getBeanConfigurationForHierarchy(BeanMetaDataManagerImpl.java:234) ~[na:na]
at org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl.createBeanMetaData(BeanMetaDataManagerImpl.java:201) ~[na:na]
at org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl.getBeanMetaData(BeanMetaDataManagerImpl.java:165) ~[na:na]
at org.hibernate.validator.internal.engine.ValidatorImpl.validateParameters(ValidatorImpl.java:267) ~[na:na]
at org.hibernate.validator.internal.engine.ValidatorImpl.validateParameters(ValidatorImpl.java:235) ~[na:na]
at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:110) ~[na:na]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[na:na]
at org.springframework.aop.framework.Interceptors$DynamicAdvisedInterceptor.intercept(Interceptors.java:120) ~[na:na]
at br.com.rj.systems.pocserviceproviders.external.controller.ServiceProviderController$$SpringProxy$43f1de91.findAll(Unknown Source) ~[poc-service-providers:0.0.1-SNAPSHOT]
at java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) ~[na:na]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150) ~[na:na]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) ~[na:na]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895) ~[na:na]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) ~[na:na]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[na:na]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1067) ~[na:na]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963) ~[na:na]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[na:na]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[na:na]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:655) ~[poc-service-providers:4.0.FR]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[na:na]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:764) ~[poc-service-providers:4.0.FR]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227) ~[na:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[na:na]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[poc-service-providers:9.0.60]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[na:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[na:na]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[na:na]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[na:na]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[na:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[na:na]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[na:na]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[na:na]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[na:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[na:na]
at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:96) ~[na:na]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[na:na]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[na:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[na:na]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[na:na]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[na:na]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[na:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[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:541) ~[na:na]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135) ~[na:na]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[poc-service-providers:9.0.60]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[na:na]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360) ~[na:na]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399) ~[na:na]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[na:na]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:889) ~[na:na]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1743) ~[na:na]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[na:na]
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 java.lang.Thread.run(Thread.java:829) ~[na:na]
at com.oracle.svm.core.thread.JavaThreads.threadStartRoutine(JavaThreads.java:597) ~[na:na]
at com.oracle.svm.core.posix.thread.PosixJavaThreads.pthreadStartRoutine(PosixJavaThreads.java:194) ~[na:na]
Edit: I'm using spring-boot-starter-parent version 2.6.5 and spring-native.version 0.11.3.
If you'd like us to spend some time investigating, please take the time to provide a complete minimal sample (something that we can unzip or git clone, build, and deploy) that reproduces the problem.
I created a example here: https://github.com/RichardJd/custom-validation-spring-native
Bellow the commands i execute:
-
build and generate native image:
mvn -DskipTests=true -Pnative clean package -
execute image:
./target/custom-validation
Access endpoint: http://localhost:8080/animes?id=1 (here the error will happen).
If execute jar how bellow, and access endpoint it's not happen error.
execute jar: java -jar ./target/custom-validation-0.0.1-SNAPSHOT-exec.jar
Thanks for the example.
The reason this fails is because the validator uses reflection on the custom validation annotation to call the message method. This method is not in the native image, because reflection hints are missing.
This is a bug in spring-native, as we could find all @Constraint annotated annotations and then generate reflection hints for them.
I've moved the issue to the backlog. We will take care of this in Spring Boot 3 / Spring Framework 6. In the meantime, you can work around by adding @TypeHints.
Spring Native is now superseded by Spring Boot 3 official native support, see the related reference documentation for more details.
As a consequence, I am closing this issue, and recommend trying your use case with latest Spring Boot 3 version. If you still experience the issue reported here, please open an issue directly on the related Spring project (Spring Framework, Data, Security, Boot, Cloud, etc.) with a reproducer.
Thanks for your contribution on the experimental Spring Native project, we hope you will enjoy the official native support introduced by Spring Boot 3.