For my springboot application / framework, I would like to create a custom Constraint Validator to validate Configuration Properties upon application startup. Based upon a different boolean property, I would like to conditionally log and suppress the constraint instead of failing application startup.
As an example, consider a custom annotation DeprecatedField
which based upon the property my.props.fail-on-deprecation
would either:
- fail application startup (if an annotated property is non-null and
my.props.fail-on-deprecation==true
) - or create a warning log and continue with startup (if an annotated property is non-null and
my.props.fail-on-deprecation==false
)
Example Classes
// DeprecatedField.java
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = DeprecatedFieldValidator.class)
public @interface DeprecatedField {
String message() default "the property is deprecated and should be removed";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
// DeprecatedFieldValidator.java
@Slf4j
@RequiredArgsConstructor
public class DeprecatedFieldValidator implements ConstraintValidator<DeprecatedField, Object> {
@Override
public void initialize(DeprecatedField annotation) {}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
// warn / error if the annotated property is used
return value == null;
}
}
// CustomConfig.java
@Slf4j
@Data
@Component
@Validated
@ConfigurationProperties(prefix = "my.custom.props", ignoreUnknownFields = false)
public class CustomConfig {
@NotBlank
private String foo;
@DeprecatedField
private String bar;
Desired Outcome
-
If the
application.yml
properties contained:my: props: fail-on-deprecation: true custom: props: foo: "value1" bar: "value2"
Then I would want the constraint to fail application startup ie:
*************************** APPLICATION FAILED TO START *************************** Description: Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'my.custom.props' to com.CustomConfig failed: Property: my.custom.props.bar Value: "value2" Reason: the property is deprecated and should be removed
-
If the
application.yml
properties contained:my: props: fail-on-deprecation: false custom: props: foo: "value1" bar: "value2"
Then I would want to suppress the constraint and start the application, but add a warning log with similar information
WARN - Deprecated Properties Violation Property: my.custom.props.bar Value: "value2" Reason: the property is deprecated and should be removed
I have tried 2 options
-
Option (1) Autowire the boolean property into the constraint validator and conditionally generate the violation
Something similar to
// DeprecatedFieldValidator.java @Slf4j @RequiredArgsConstructor public class DeprecatedFieldValidator implements ConstraintValidator<DeprecatedField, Object> { @Value("${my.props.fail-on-deprecation:false}" private boolean failOnDeprecation; @Override public void initialize(DeprecatedField annotation) {} @Override public boolean isValid(Object value, ConstraintValidatorContext context) { // warn if not failing and the annotated property is used if (!failOnDeprecation) { // TODO: not sure how to get the full property path of "my.custom.props.bar" for this log log.warn("WARN - Deprecated Properties Violation"); } // fail only if fail flag enabled and deprecated property are used return !failOnDeprecation || value == null; } }
-
Option (2) Create a custom
configurationPropertiesValidator
bean that is a child of LocalValidatorFactoryBean and overrides theprocessConstraintViolations
method to filter the violations of our customDeprecatedField
annotation// DeprecationValidatorFactoryBean.java @Slf4j @Component("configurationPropertiesValidator") @RequiredArgsConstructor public class DeprecationValidatorFactoryBean extends LocalValidatorFactoryBean { @Value("${my.props.fail-on-deprecation:false}" private boolean fail; @Override protected void processConstraintViolations(Set<ConstraintViolation<Object>> violations, Errors errors) { // if fail==true - then return all violations // if fail==false - then filter (remove) and log DeprecationField Violations Set<ConstraintViolation<Object>> deprecationFilteredViolations = (fail) ? violations : this.filterDeprecationViolations(violations, errors); super.processConstraintViolations(deprecationFilteredViolations, errors); } // ... }
Option (1) functionally works, but I’m not sure how to access the property path (ie: my.custom.props.bar
) for the warning log
Option (2) seems to work, but seems more complex than in may need to be as this approach requires indirect usage of the "configurationPropertiesValidator"
bean and the custom annotations to function. If another dependency happens to use the "configurationPropertiesValidator"
, then there will be conflict.
Question:
- How can the desired behavior be achieved?
- Is there a way to achieve the desired logging outcome (with the full property name) using option (1)?
- Is there another recommended pattern via spring / spring boot for achieving this desired behavior?
user25911120 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.