Search code examples
spring-bootcustomvalidatorspring-validatorspring-validation

How can I create custom validator on Java List type?


I have one NumberConstraint as follows:

@Constraint(validatedBy = { StringConstraintValidator.class, })
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.PARAMETER, })
public @interface StringConstraint {
   
    String message() default "'${validatedValue}' ist not valid Number. " +
            "A String is composed of 7 characters (digits and capital letters). Valid example: WBAVD13.";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

and it is validated by StringConstraintValidator as follows:

@Component
public class StringConstraintValidator implements ConstraintValidator<StringConstraint, String> {

    private static final String REGEX_String = "^[A-Z0-9]{7}$";

    @Override
    public void initialize(final StringConstraint annotation) {
        // noop
    }

    @Override
    public boolean isValid(final String value, final ConstraintValidatorContext context) {
        return isValid(value);
    }

    public boolean isValid(final String value) {
        // Number is not always null Checked via @NotNull
        LoggingContext.get().setString(value);
        if (value == null) {
            return false;
        }
        return Pattern.compile(REGEX_STRING).matcher(value).matches();
    }
}

I can apply this StringConstraint on single field of request object as follows:

@StringConstraint
private String number;

but if my request object contains a List of Strings then how can I use this constraint on entire List or do I have to define new on List type ?? Something like ConstraintValidator<StringConstraint, List ???>

My request object is:

    @JsonProperty(value = "items", required = true)
    @Schema(description = "List of items.", required = true)
    private List<String> items= new ArrayList<>();

So i want to apply my validator on all the strings in the list. How can i apply @StringConstraint on my list ?


Solution

  • Yes, you can add more validators for one constraint by using a comma-separated list of validator classes in the validatedBy attribute of the @Constraint annotation. For example, you can write:

    @Constraint(validatedBy = {StringConstraintValidator.class, BlablaValidot.class})
    public @interface MyConstraint {
      // other attributes
    }
    

    Explanation

    The @Constraint annotation is used to define a custom constraint annotation that can be applied to fields, methods, classes, etc. The validatedBy attribute specifies one or more classes that implement the ConstraintValidator interface and provide the logic to validate the annotated element. You can use multiple validators for the same constraint if you want to check different aspects or conditions of the value. For example, you can have one validator that checks the length of a string and another that checks the format of a string.

    Examples

    Here are some examples of custom constraint annotations with multiple validators:

    • A @PhoneNumber annotation that validates a phone number using two validators: one for the country code and one for the number format.
    @Constraint(validatedBy = {CountryCodeValidator.class, PhoneNumberValidator.class})
    @Target({ElementType.FIELD, ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface PhoneNumber {
      String message() default "Invalid phone number";
      Class<?>[] groups() default {};
      Class<? extends Payload>[] payload() default {};
    }
    
    • A @Password annotation that validates a password using three validators: one for the minimum length, one for the maximum length, and one for the presence of special characters.
    @Constraint(validatedBy = {MinLengthValidator.class, MaxLengthValidator.class, SpecialCharValidator.class})
    @Target({ElementType.FIELD, ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Password {
      String message() default "Invalid password";
      Class<?>[] groups() default {};
      Class<? extends Payload>[] payload() default {};
      int minLength() default 8;
      int maxLength() default 20;
      String specialChars() default "!@#$%^&*";
    }