Spring Boot version: 2.4.3-SNAPSHOT
Hi.
I've a @ConfigurationProperties
annotated class which implements the Validator
interface to validate some fields, but the method in the subject is never called causing the validation to fail.
In particular, I've the following structure:
public class SecurityRequestExtractionProperties {
private CookieExtractionNames cookie = new CookieExtractionNames();
private HeaderExtractionNames header = new HeaderExtractionNames();
// getters and setters here
public static class CookieExtractionNames {
private String requestId = "My-Request-ID";
private String sessionId = "My-Session-ID";
// getters and setters here
}
public static class HeaderExtractionNames {
private String requestId = "My-Request-ID";
private String sessionId = "My-Session-ID";
private String tenant = "My-Tenant";
// getters and setters here
}
}
and
@ConfigurationProperties(prefix = SpringSecurityRequestExtractionProperties.PREFIX)
public class SpringSecurityRequestExtractionProperties
extends SecurityRequestExtractionProperties implements Validator {
public static final String PREFIX = "acme.security.request.extraction";
@Override
public boolean supports(Class<?> clazz) {
return clazz == SpringSecurityRequestExtractionProperties.class;
}
@Override
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmpty(errors, "cookie", "cookie.empty");
}
}
In Spring Boot 2.1.2 the default values specified in the class where used when no value was explicitly configured, but in the specified version the validation actually fails with
Caused by: org.springframework.boot.context.properties.bind.validation.BindValidationException: Binding validation errors on acme.security.request.extraction
- Field error in object 'acme.security.request.extraction' on field 'cookie': rejected value [null]; codes [cookie.empty.acme.security.request.extraction.cookie,cookie.empty.cookie,cookie.empty.acme.commons.security.api.SecurityRequestExtractionProperties$CookieExtractionNames,cookie.empty]; arguments []; default message [null]
Comment From: wilkinsona
Thanks for the report, but I'm not sure what you're trying to do. A @ConfigurationProperties
class shouldn't be a Validator
. A Validator
is a general-purpose contract for performing validation and isn't specific to any individual @ConfigurationProperties
class. If you want to provide a validator for the validation of @ConfigurationProperties
beans then, as described in the documentation, you should define a bean named configurationPropertiesValidator
.
Comment From: cdprete
I've read that documentation and we're actually migrating our code from Spring Boot 2.1.2 to 2.4.3 and, as already said, before this was working fine (and nicer than having a single configurationPropertiesValidator
bean 'cause the validation was encapsulated).
Comment From: wilkinsona
Unfortunately, if your arrangement worked in 2.1.2 then I believe that was by accident rather than design. The documentation for 2.1.2 describes the same approach using a configurationPropertiesValidator
bean.
Comment From: cdprete
How can you have multiple validators then? It seems impossible to me then.
Comment From: wilkinsona
Sorry, I was mistaken above. I'd forgotten that we'd added support for a @ConfigurationProperties
bean itself being a Validator
.
The change in behaviour appears to be a side-effect of the fix for https://github.com/spring-projects/spring-boot/issues/17424. BeanPropertyBindingResult
will extract a value from the underlying target whereas ValidationResult
will not. This means that the latter can only see values that have been bound while the former can also see default values.
ValidationBindHandler#onSuccess
is a red herring here as it's only called when a property's been bound. That will never happen when relying purely on default values.
Comment From: cdprete
I've just followed the flow from ValidationUtils.rejectIfEmpty
and ended up in ValidationBindHandler
:)