Search code examples
javabean-validationhibernate-validator

Multiple constraint annotations confused on Java Bean Validation


I am confused about the case that have multiple constraint annotations on a field, below:

    public class Student
{
    @NotNull
    @Size(min = 2, max = 14, message = "The name '${validatedValue}' must be between {min} and {max} characters long")
    private String name;

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }
}

Test case:

public class StudentTest
{
    private static Validator validator;

    @BeforeClass
    public static void setUp()
    {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        validator = factory.getValidator();

        System.out.println(Locale.getDefault());
    }

    @Test
    public void nameTest()
    {
        Student student = new Student();
        student.setName(null);

        Set<ConstraintViolation<Student>> constraintViolations = validator.validateProperty(student, "name");

        System.out.println(constraintViolations.size());
        System.out.println(constraintViolations.iterator().next().getMessage());

    }

}

The result is:

1 
Can't be null

That is, when the @NotNull constraint is violated, it will not continue. Yes, this is the right situation. When one check is failed, we don't want it check the next constraint. But the situation is different when I used custom constraint.

I defined two custom constraints ACheck and BCheck.

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = { ACheckValidator.class })
public @interface ACheck
{
    String message() default "A check error";

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { };
}

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = { BCheckValidator.class })
public @interface BCheck
{
    String message() default "B check error";

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { };
}

public class ACheckValidator implements ConstraintValidator<ACheck, String>
{

    public void initialize(ACheck constraintAnnotation)
    {
    }

    public boolean isValid(String value, ConstraintValidatorContext context)
    {
        return false;
    }

}

public class BCheckValidator implements ConstraintValidator<BCheck, String>
{

    public void initialize(BCheck constraintAnnotation)
    {
    }

    public boolean isValid(String value, ConstraintValidatorContext context)
    {
        return false;
    }

}

There is not specific info about custom constraint, and I change the Student.java and use custom constraint like that:

@ACheck
@BCheck
private String name;

Test again, and the result is:

2
B check error

That is, when the @ACheck constraint is violatedm, it also wil check @BCheck, Why this happens, anything else I had ignored?


Solution

  • when the @NotNull constraint is violated, it will not continue

    That is incorrect. It will continue checking all the other constraints. It's just that the Size validator considers a null value as an acceptable value. The reason is that typically, you want

    • a non-null, minimum size value: then you apply both constraints
    • or a nullable value, which must have a minimum size if the value is present: then you only apply Size.