Question

I am using two validation annotations on a property in the bean:

@NotEmpty(message = "{name.required}")
@Pattern(regex = "^([A-Za-z0-9]{2,}(\\-[a-zA-Z0-9])?)$", message = "{invalid.name}")
private String name;

If i left the name empty, I got the two errors but I want only the first error message (if the first condition occurs show its error message then skip the second condition).

Was it helpful?

Solution

if the first condition occurs show its error message then skip the second condition

This can be done by creating Composite Constraint and annotating it with @ReportAsSingleViolation meta constraint.

UserName.java

@ReportAsSingleViolation
@NotEmpty
@Pattern(regexp="^([A-Za-z0-9]{2,}(\\-[a-zA-Z0-9])?)$")
@Constraint(validatedBy = {})
public @interface UserName {
    String message() default "invalid userName!";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

Reference 3.2. Constraint composition

OTHER TIPS

The accepted answer doesn't work as you expect. Sure constraint composition is good only if you want to list ALL the errors in the ENTIRE composition chain. It doesn't work if you want to early exit from the first validation error.

The docs for @ReportAsSingleViolation say The error reports of each individual composing constraint are ignored.

Using the accept example

@ReportAsSingleViolation
@NotEmpty
@Pattern(regexp="^([A-Za-z0-9]{2,}(\\-[a-zA-Z0-9])?)$")
@Constraint(validatedBy = {})
public @interface UserName {
    String message() default "invalid userName!";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

This means you will get the default message error of the UserName annotation which is "invalid userName!" even if @NotEmpty fails first....

I must say I am quite shocked at how poor this design is by the java bean validation implementors. It makes absolutely no sense to have composed validations if you return a completely irrelevant message. It should fail first AND return the corresponding error for the validation that actually failed!. Anyway there is no way to do this without massive ugly hacks. Such a simple validation task turns into a nightmare. 0_o

My work around solution is don't compose validations, just create 1 validation and implement it all yourself. Its not DRY but its simple at least.

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PasswordValidator.class)
public @interface Password {
    String message() default "{com.example.Password.message}";

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

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



public class PasswordValidator implements ConstraintValidator<Password, String> {
    private Pattern twoDigitsPattern;

    public void initialize(Password constraint) {
        twoDigitsPattern = Pattern.compile("(.*[\\d]){2}");
    }

    public boolean isValid(String password, ConstraintValidatorContext context) {
        context.disableDefaultConstraintViolation();

        if (password == null) {
            context.buildConstraintViolationWithTemplate("{javax.validation.constraints.NotNull.message}")
                    .addConstraintViolation();
            return false;
        }

        if (password.length() < 5 || password.length() > 10) {
            context.buildConstraintViolationWithTemplate("must be between 5 to 10 characters")
                    .addConstraintViolation();
            return false;
        }

        if (!twoDigitsPattern.matcher(password).matches()) {
            context.buildConstraintViolationWithTemplate("must contain 2 digits between [0-9]").addConstraintViolation();
            return false;
        }

        return true;
    }

}

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top