4

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

0

2 Answers 2

5

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

Sign up to request clarification or add additional context in comments.

1 Comment

Is the order of these@NotEmpty and @Pattern annotations being ignored ? Also are they run before the custom constraint ?
4

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;
    }

}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.