Home > front end >  Java @NotNull violation message to show other constraints' details
Java @NotNull violation message to show other constraints' details

Time:12-14

Given a username field which cannot be null, must be between 4 and 64 characters long and match the regexp [A-Za-z0-9] , when the username field is null, the error message is simply: must not be null. The desired outcome is must not be null AND length must be between 4 and 64 characters AND must match "[A-Za-z0-9] ".

Initial setup:

@NotNull
@Length(min = 4, max = 64)
@Pattern(regexp = "[A-Za-z0-9] ")
String username;

What I also tried:

@NotNull(message = """
        {jakarta.validation.constraints.NotNull.message} 
        AND {org.hibernate.validator.constraints.Length.message} 
        AND {jakarta.validation.constraints.Pattern.message}""")
@Length(min = 4, max = 64)
@Pattern(regexp = "[A-Za-z0-9] ")
String username;

But this latter's outcome is:

ConstraintViolationImpl{
  interpolatedMessage='must not be null AND length must be between {min} and {max} AND must match "{regexp}"',
  propertyPath=username, rootBeanClass=class app.User,
  messageTemplate='{jakarta.validation.constraints.NotNull.message} AND {org.hibernate.validator.constraints.Length.message} AND {jakarta.validation.constraints.Pattern.message}'}

The values for the constraints (min, max, regexp) are not accessed. How to render the actual values of these in the error message?

CodePudding user response:

Most constraint annotations, including @Length and @Pattern, regard null as valid input. That's why you won't get what you want by just using these annotations.

Fortunately, it's really easy to do what you want by introducing a new constraint annotation:

@Constraint(validatedBy = {}) // no validator needed, it delegates to other constraints
@NotNull
@Length(min = 4, max = 64)
@Pattern(regexp = "[A-Za-z0-9] ")
@ReportAsSingleViolation // to prevent separate violations for the other constraints
@Target(FIELD)
@Retention(RUNTIME)
public @interface ValidUsername {

    String message() default """
        {jakarta.validation.constraints.NotNull.message} 
        AND {org.hibernate.validator.constraints.Length.message} 
        AND {jakarta.validation.constraints.Pattern.message}""";

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

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

This is annotated with your constraints, which means these are applied when this annotation is checked.

The message will still contain place holders {min} etc., because these are not properties of this constraint itself. You can solve that in two ways:

  1. Don't use the templates but hard-code the message
  2. Add these properties to your annotation as well, and tell the Bean Validation framework to use these for the other annotations:
    @OverridesAttribute(constraint = Length.class)
    int min() default 4;

    @OverridesAttribute(constraint = Length.class)
    int max() default 64;

    @OverridesAttribute(constraint = Pattern.class)
    String regexp() default "[A-Za-z0-9] ";

Now all you need to do is replace the annotations on the username field with @ValidUsername.

  • Related