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
This commit is contained in:
Andy Wilkinson
2026-01-15 14:12:33 +00:00
parent e8cea82772
commit 625571c3d1
4 changed files with 48 additions and 0 deletions
@@ -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"));
}
}
}
@@ -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<ReactiveWebApplicationContext> assertExchangeWithSession(
Consumer<MockServerWebExchange> exchange) {
return (context) -> {