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 :)