From 625571c3d10ca335cef9d884ec697e69efe8f034 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 15 Jan 2026 14:12:33 +0000 Subject: [PATCH] Fix MVC and WebFlux validator creation in a native image Following modularization, a presence check for ValidatorAdapter was needed in the MVC and WebFlux auto-configuratiomn when creating their Validators. Runtime hints to allow this check to work in a native image were not added at the same time, resulting in the class appearing to be absent. This caused message interpolation for constraint violations to fail as newly created Validator was being used which lacked the necessary MessageInterpolator configuration. This commit adds reflection hints for ValidatorAdapter, allowing re-use of the context's main validator as the MVC and WebFlux validators. Fixes gh-48828 --- .../autoconfigure/WebFluxAutoConfiguration.java | 14 ++++++++++++++ .../WebFluxAutoConfigurationTests.java | 10 ++++++++++ .../autoconfigure/WebMvcAutoConfiguration.java | 14 ++++++++++++++ .../WebMvcAutoConfigurationTests.java | 10 ++++++++++ 4 files changed, 48 insertions(+) diff --git a/module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/autoconfigure/WebFluxAutoConfiguration.java b/module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/autoconfigure/WebFluxAutoConfiguration.java index f06694403cc..d19f3a49b06 100644 --- a/module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/autoconfigure/WebFluxAutoConfiguration.java +++ b/module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/autoconfigure/WebFluxAutoConfiguration.java @@ -25,6 +25,9 @@ import org.apache.commons.logging.LogFactory; import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.aot.hint.TypeReference; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -308,6 +311,7 @@ public final class WebFluxAutoConfiguration { */ @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties({ WebProperties.class, ServerProperties.class }) + @ImportRuntimeHints(WebFluxValidatorRuntimeHints.class) static class EnableWebFluxConfiguration extends DelegatingWebFluxConfiguration { private final WebFluxProperties webFluxProperties; @@ -452,4 +456,14 @@ public final class WebFluxAutoConfiguration { } + static class WebFluxValidatorRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { + hints.reflection() + .registerType(TypeReference.of("org.springframework.boot.validation.autoconfigure.ValidatorAdapter")); + } + + } + } diff --git a/module/spring-boot-webflux/src/test/java/org/springframework/boot/webflux/autoconfigure/WebFluxAutoConfigurationTests.java b/module/spring-boot-webflux/src/test/java/org/springframework/boot/webflux/autoconfigure/WebFluxAutoConfigurationTests.java index 7ca7f5ea8a3..43084d0f1c4 100644 --- a/module/spring-boot-webflux/src/test/java/org/springframework/boot/webflux/autoconfigure/WebFluxAutoConfigurationTests.java +++ b/module/spring-boot-webflux/src/test/java/org/springframework/boot/webflux/autoconfigure/WebFluxAutoConfigurationTests.java @@ -46,6 +46,8 @@ import org.junit.jupiter.params.provider.ValueSource; import reactor.core.publisher.Mono; import org.springframework.aop.support.AopUtils; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration; import org.springframework.boot.http.codec.CodecCustomizer; @@ -59,6 +61,7 @@ import org.springframework.boot.web.context.reactive.ReactiveWebApplicationConte import org.springframework.boot.web.server.autoconfigure.ServerProperties; import org.springframework.boot.web.server.reactive.MockReactiveWebServerFactory; import org.springframework.boot.webflux.autoconfigure.WebFluxAutoConfiguration.WebFluxConfig; +import org.springframework.boot.webflux.autoconfigure.WebFluxAutoConfiguration.WebFluxValidatorRuntimeHints; import org.springframework.boot.webflux.autoconfigure.WebFluxAutoConfigurationTests.OrderedControllerAdviceBeansConfiguration.HighestOrderedControllerAdvice; import org.springframework.boot.webflux.autoconfigure.WebFluxAutoConfigurationTests.OrderedControllerAdviceBeansConfiguration.LowestOrderedControllerAdvice; import org.springframework.boot.webflux.filter.OrderedHiddenHttpMethodFilter; @@ -907,6 +910,13 @@ class WebFluxAutoConfigurationTests { }); } + @Test + void registersRuntimeHintsForValidatorCreation() { + RuntimeHints hints = new RuntimeHints(); + new WebFluxValidatorRuntimeHints().registerHints(hints, getClass().getClassLoader()); + assertThat(RuntimeHintsPredicates.reflection().onType(ValidatorAdapter.class)).accepts(hints); + } + private ContextConsumer assertExchangeWithSession( Consumer exchange) { return (context) -> { diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration.java index 28e042fb836..da1e334a302 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration.java @@ -28,6 +28,9 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jspecify.annotations.Nullable; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.aot.hint.TypeReference; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; @@ -435,6 +438,7 @@ public final class WebMvcAutoConfiguration { */ @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(WebProperties.class) + @ImportRuntimeHints(MvcValidatorRuntimeHints.class) static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware { private final Resources resourceProperties; @@ -724,4 +728,14 @@ public final class WebMvcAutoConfiguration { } + static class MvcValidatorRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { + hints.reflection() + .registerType(TypeReference.of("org.springframework.boot.validation.autoconfigure.ValidatorAdapter")); + } + + } + } diff --git a/module/spring-boot-webmvc/src/test/java/org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfigurationTests.java b/module/spring-boot-webmvc/src/test/java/org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfigurationTests.java index 362228a292c..38c329ca428 100644 --- a/module/spring-boot-webmvc/src/test/java/org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfigurationTests.java +++ b/module/spring-boot-webmvc/src/test/java/org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfigurationTests.java @@ -45,6 +45,8 @@ import org.junit.jupiter.api.Test; import org.mockito.InOrder; import org.springframework.aop.support.AopUtils; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration; @@ -64,6 +66,7 @@ import org.springframework.boot.web.server.servlet.ServletWebServerFactory; import org.springframework.boot.web.server.servlet.context.AnnotationConfigServletWebServerApplicationContext; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration.MvcValidatorRuntimeHints; import org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter; import org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfigurationTests.OrderedControllerAdviceBeansConfiguration.HighestOrderedControllerAdvice; import org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfigurationTests.OrderedControllerAdviceBeansConfiguration.LowestOrderedControllerAdvice; @@ -1137,6 +1140,13 @@ class WebMvcAutoConfigurationTests { }); } + @Test + void registersRuntimeHintsForValidatorCreation() { + RuntimeHints hints = new RuntimeHints(); + new MvcValidatorRuntimeHints().registerHints(hints, getClass().getClassLoader()); + assertThat(RuntimeHintsPredicates.reflection().onType(ValidatorAdapter.class)).accepts(hints); + } + private void assertResourceHttpRequestHandler(AssertableWebApplicationContext context, Consumer handlerConsumer) { Map handlerMap = getHandlerMap(context.getBean("resourceHandlerMapping", HandlerMapping.class));