diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/external-config.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/external-config.adoc index b170d6ae624..8a638dad0b8 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/external-config.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/external-config.adoc @@ -1178,7 +1178,7 @@ include-code::MyProperties[] TIP: You can also trigger validation by annotating the `@Bean` method that creates the configuration properties with `@Validated`. -To ensure that validation is always triggered for nested properties, even when no properties are found, the associated field must be annotated with `@Valid`. +To cascade validation to nested properties the associated field must be annotated with `@Valid`. The following example builds on the preceding `MyProperties` example: include-code::nested/MyProperties[] diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java index 673f0dcadaf..2e592e305a7 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java @@ -76,8 +76,6 @@ class ConfigurationPropertiesBinder { private final boolean jsr303Present; - private volatile Validator jsr303Validator; - private volatile Binder binder; ConfigurationPropertiesBinder(ApplicationContext applicationContext) { @@ -141,7 +139,7 @@ class ConfigurationPropertiesBinder { validators.add(this.configurationPropertiesValidator); } if (this.jsr303Present && target.getAnnotation(Validated.class) != null) { - validators.add(getJsr303Validator()); + validators.add(getJsr303Validator(target.getType().resolve())); } Validator selfValidator = getSelfValidator(target); if (selfValidator != null) { @@ -162,11 +160,8 @@ class ConfigurationPropertiesBinder { return null; } - private Validator getJsr303Validator() { - if (this.jsr303Validator == null) { - this.jsr303Validator = new ConfigurationPropertiesJsr303Validator(this.applicationContext); - } - return this.jsr303Validator; + private Validator getJsr303Validator(Class type) { + return new ConfigurationPropertiesJsr303Validator(this.applicationContext, type); } private List getBindHandlerAdvisors() { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesJsr303Validator.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesJsr303Validator.java index 5308a71b53c..afc63041686 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesJsr303Validator.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesJsr303Validator.java @@ -37,13 +37,16 @@ final class ConfigurationPropertiesJsr303Validator implements Validator { private final Delegate delegate; - ConfigurationPropertiesJsr303Validator(ApplicationContext applicationContext) { + private final Class validatedType; + + ConfigurationPropertiesJsr303Validator(ApplicationContext applicationContext, Class validatedType) { this.delegate = new Delegate(applicationContext); + this.validatedType = validatedType; } @Override public boolean supports(Class type) { - return this.delegate.supports(type); + return this.validatedType.equals(type) && this.delegate.supports(type); } @Override diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java index 7b78b1fe96d..588b28f2e60 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java @@ -106,6 +106,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatException; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.Assertions.entry; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; @@ -525,10 +526,9 @@ class ConfigurationPropertiesTests { } @Test - void loadWhenJsr303ConstraintDoesNotMatchOnNestedThatIsNotDirectlyAnnotatedShouldFail() { - assertThatExceptionOfType(ConfigurationPropertiesBindException.class) - .isThrownBy(() -> load(ValidatedNestedJsr303Properties.class, "properties.description=")) - .withCauseInstanceOf(BindException.class); + void loadWhenJsr303ConstraintDoesNotMatchOnNestedThatIsNotAnnotatedWithValidShouldNotFail() { + assertThatNoException() + .isThrownBy(() -> load(ValidatedNestedJsr303Properties.class, "properties.description=")); } @Test @@ -1837,7 +1837,7 @@ class ConfigurationPropertiesTests { @Validated static class ValidatedNestedJsr303Properties { - private Jsr303Properties properties; + private final Jsr303Properties properties = new Jsr303Properties(); Jsr303Properties getProperties() { return this.properties; @@ -1851,9 +1851,9 @@ class ConfigurationPropertiesTests { static class ValidatedValidNestedJsr303Properties { @Valid - private final List properties = Collections.singletonList(new Jsr303Properties()); + private final Jsr303Properties properties = new Jsr303Properties(); - List getProperties() { + Jsr303Properties getProperties() { return this.properties; }