Prior to this commit, AOT processing for bean validation failed with NoClassDefFoundError for constraints with missing dependencies. With this change, the processing no longer fails, and a warning is logged instead.

  • Fixes #33940
Example Log Message
16:16:31.245 [Test worker] WARN  o.s.v.b.BeanValidationBeanRegistrationAotProcessor - Skipping validation constraint hint inference for class org.springframework.validation.beanvalidation.BeanValidationBeanRegistrationAotProcessorTests$ConstraintWithMissingDependency
java.lang.NoClassDefFoundError: org.springframework.validation.beanvalidation.BeanValidationBeanRegistrationAotProcessorTests$Filtered
    at org.springframework.validation.beanvalidation.BeanValidationBeanRegistrationAotProcessorTests$FilteringClassLoader.loadClassForOverriding(BeanValidationBeanRegistrationAotProcessorTests.java:278) ~[test/:?]
    at org.springframework.core.OverridingClassLoader.loadClass(OverridingClassLoader.java:88) ~[spring-core-7.0.0-SNAPSHOT.jar:7.0.0-SNAPSHOT]
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525) ~[?:?]
    at org.springframework.core.OverridingClassLoader.loadClass(OverridingClassLoader.java:82) ~[spring-core-7.0.0-SNAPSHOT.jar:7.0.0-SNAPSHOT]
    at java.base/java.lang.Class.getDeclaredFields0(Native Method) ~[?:?]
    at java.base/java.lang.Class.privateGetDeclaredFields(Class.java:3297) ~[?:?]
    at java.base/java.lang.Class.getDeclaredFields(Class.java:2371) ~[?:?]
    at org.hibernate.validator.internal.util.privilegedactions.GetDeclaredFields.run(GetDeclaredFields.java:30) ~[hibernate-validator-7.0.5.Final.jar:7.0.5.Final]
    at org.hibernate.validator.internal.util.privilegedactions.GetDeclaredFields.run(GetDeclaredFields.java:17) ~[hibernate-validator-7.0.5.Final.jar:7.0.5.Final]
    at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.run(AnnotationMetaDataProvider.java:602) ~[hibernate-validator-7.0.5.Final.jar:7.0.5.Final]
    at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.getFieldMetaData(AnnotationMetaDataProvider.java:217) ~[hibernate-validator-7.0.5.Final.jar:7.0.5.Final]
    at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.retrieveBeanConfiguration(AnnotationMetaDataProvider.java:130) ~[hibernate-validator-7.0.5.Final.jar:7.0.5.Final]
    at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.getBeanConfiguration(AnnotationMetaDataProvider.java:121) ~[hibernate-validator-7.0.5.Final.jar:7.0.5.Final]
    at org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl.getBeanConfigurationForHierarchy(BeanMetaDataManagerImpl.java:234) ~[hibernate-validator-7.0.5.Final.jar:7.0.5.Final]
    at org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl.createBeanMetaData(BeanMetaDataManagerImpl.java:201) ~[hibernate-validator-7.0.5.Final.jar:7.0.5.Final]
    at org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl.getBeanMetaData(BeanMetaDataManagerImpl.java:165) ~[hibernate-validator-7.0.5.Final.jar:7.0.5.Final]
    at org.hibernate.validator.internal.engine.ValidatorImpl.getConstraintsForClass(ValidatorImpl.java:316) ~[hibernate-validator-7.0.5.Final.jar:7.0.5.Final]
    at org.springframework.validation.beanvalidation.BeanValidationBeanRegistrationAotProcessor$BeanValidationDelegate.processAheadOfTime(BeanValidationBeanRegistrationAotProcessor.java:125) ~[main/:?]
    at org.springframework.validation.beanvalidation.BeanValidationBeanRegistrationAotProcessor$BeanValidationDelegate.processAheadOfTime(BeanValidationBeanRegistrationAotProcessor.java:110) ~[main/:?]
    at org.springframework.validation.beanvalidation.BeanValidationBeanRegistrationAotProcessor.processAheadOfTime(BeanValidationBeanRegistrationAotProcessor.java:75) ~[main/:?]
    at org.springframework.validation.beanvalidation.BeanValidationBeanRegistrationAotProcessorTests.createContribution(BeanValidationBeanRegistrationAotProcessorTests.java:144) ~[test/:?]
    at org.springframework.validation.beanvalidation.BeanValidationBeanRegistrationAotProcessorTests.process(BeanValidationBeanRegistrationAotProcessorTests.java:134) ~[test/:?]
    at org.springframework.validation.beanvalidation.BeanValidationBeanRegistrationAotProcessorTests.shouldSkipConstraintWithMissingDependency(BeanValidationBeanRegistrationAotProcessorTests.java:129) ~[test/:?]

Comment From: odrotbohm

I still think that we should avoid forwarding bean types of BeanDefinitions that don't use validation annotations by definition. Simply to avoid unnecessary work.

Comment From: scordio

Thanks for the reminder, @odrotbohm! While working on this PR, I completely forgot about https://github.com/spring-projects/spring-framework/issues/33940#issuecomment-2493679646, sorry about that.

I just pushed a possible solution for beans with ROLE_INFRASTRUCTURE. Do you think beans of type ROLE_SUPPORT are also relevant?

@sbrannen mentioned that he plans to discuss this change with the team next week.

Comment From: snicoll

The role infrastructure looks a little bit arbitrary to me. I'd prefer if we use a signal that's more direct to the task at hand.

Comment From: scordio

@snicoll for the scope of this PR, I propose I revert 5a50fcb and keep only 8753789 to address the issue reported at #33940, and I would leave the decision of further enhancements to the team.

WDYT?

Comment From: snicoll

Thanks for the suggestion. We can keep things as they are or drop the commit based on what the team decides.

Comment From: edeandrea

Hi folks - do you know if there is any workaround for this issue? Currently it blocks an upgrade to SB 3.4.0. Removing the spring-boot-starter-validation dependency isn't an option.

Comment From: sbrannen

Hi folks - do you know if there is any workaround for this issue? Currently it blocks an upgrade to SB 3.4.0. Removing the spring-boot-starter-validation dependency isn't an option.

As an interim workaround (before 6.2.1 is released), you should hopefully be able to add the dependencies causing the problem.

For example, if you're seeing NoClassDefFoundError: org/reactivestreams/Publisher, adding a dependency on io.projectreactor:reactor-core may resolve the issue for you (again as a temporary workaround).

Please let us know if that works for you.

Comment From: sbrannen

Team Decision: Merge the PR without the BeanDefinition.ROLE_INFRASTRUCTURE check.

Comment From: sbrannen

This has been merged into 6.2.x and main in 9b0253e117049c264f9ca0737eca45319f23b805 and revised in ea3bd7ae0c6e07e6f5fe7303d757efd203c0eca9.

Thanks, @scordio! 👍

Comment From: scordio

@edeandrea I can confirm that in an empty project with spring-boot-starter-data-jpa and spring-boot-starter-validation similar to what was reported at #33940, adding io.projectreactor:reactor-core (with runtime scope) allows processAot to succeed.

Just bringing the dependency might cause side effects with auto configurations like WebSessionIdResolverAutoConfiguration, depending on what else the application is doing, so my suggestion would be to evaluate the impact carefully (or just wait for the next Framework release).

Comment From: edeandrea

Thank you @scordio - adding io.projectreactor:reactor-core with runtime scope fixed my issue for now.

Comment From: simaotwx

Thank you @scordio - adding io.projectreactor:reactor-core with runtime scope fixed my issue for now.

Does not appear to work. I added it like follows:

runtimeOnly 'io.projectreactor:reactor-core:3.7.1'

But I still get this error:

java.lang.ClassNotFoundException: org.reactivestreams.Publisher
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeSuspendingFunction(InvocableHandlerMethod.java:291)
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:249)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:188)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:926)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:831)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914)
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
    at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108)
    at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:365)
    at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:100)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
    at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:126)
    at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:120)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
    at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:131)
    at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
    at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
    at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
    at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
    at ...
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:227)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:221)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:227)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:221)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107)
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
    at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90)
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
    at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82)
    at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
    at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191)
    at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113)
    at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$3(HandlerMappingIntrospector.java:195)
    at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113)
    at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74)
    at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:230)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:362)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:278)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:397)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:905)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
    at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190)
    at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
    at java.base/java.lang.Thread.run(Thread.java:1583)

ClassNotFoundException and NoClassDefFound have been haunting me all day. One would assume if a library from maven central is used, it would "just work" and have all dependencies included. Looks like that does not quite hold true all that well, especially when using spring.

The error pops up when submitting a request to the application. Spring version is 3.3.7.

Comment From: simaotwx

Update to my previous comment:

I fixed it by adding these to build.gradle:

    runtimeOnly 'io.projectreactor:reactor-core:3.7.1'
    runtimeOnly 'org.reactivestreams:reactive-streams:1.0.4'
    runtimeOnly 'org.jetbrains.kotlinx:kotlinx-coroutines-reactor:1.10.1'

And then deleting .gradle, .kotlin and build from the project directory, then using the "Invalidate caches…" feature to restart IntelliJ.

Comment From: bclozel

@simaotwx I don't think your problem is related to this issue. This issue was fixed in Spring Framework 6.2.1, a regression introduced in 6.2.0. Spring Boot 3.3.7 uses Spring Framework 6.1.x. Also, the stacktrace indicates that this problem is happening while invoking a controller method, while this issue is about AOT processing at build time.

Maybe a controller in your application is exposing a reactive type somehow and the dependency management of your application is incomplete? Maybe a library is exposing reactive support and you didn't intend to activate it?

If you can reproduce the problem with a minimal sample application, please share it with us by creating a new issue.