Search code examples
javabean-validationjsr349

How do I display the field name description in the constraint violation message of a custom constraint annotation?


How do I display the field name description in the constraint violation message of a Bean Validation 1.1 (JSR-349) custom constraint annotation?

For example, given the following custom constraint annotation @Required, resource bundle ValidationMessages.properties, and class Person, how can I compose the constraint violation message "First Name is required." for required field firstName and "Last Name is required." for required field lastName?

@Documented
@Constraint(validatedBy = {})
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@NotEmpty
@ReportAsSingleViolation
public @interface Required {
    public abstract Class<?>[] groups() default {};
    public abstract String message() default "{Required.message}";
    public abstract Class<? extends Payload>[] payload() default {};
}

In resource bundle, ValidationMessages.properties:

Required.message=is required.

Class Person:

public class Person {
    @Required
    private String firstName;

    @Required
    private String lastName;
}

Solution

  • There is no API you can use to get the current property name. And if there were, you still would do some string manipulation to get from the property name 'firstName' to the display name "First Name".

    That said, I can see the benefit of exposing the current Path in ConstraintValidatorContext which is passed to ConstraintValidator#isValid. This is per specification not possible atm, but it could be implemented as a provider specific feature. You could create a issue request for Hibernate Validator here.

    Regarding your problem, the best solution imo is to add a 'labelattribute to@Required`:

    public class Person {
        @Required(label="First Name"
        private String firstName;
    
        @Required(label="Last Name"
        private String lastName;
    }
    

    Then you can interpolate the label in the message bundle like so:

    Required.message={label} is required.
    

    The constraint would look something like this

    @Documented
    @Constraint(validatedBy = {})
    @Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @NotEmpty
    @ReportAsSingleViolation
    public @interface Required {
        public abstract String label();
        public abstract Class<?>[] groups() default {};
        public abstract String message() default "{Required.message}";
        public abstract Class<? extends Payload>[] payload() default {};
    }
    

    Note, you can add parameters you like (provided the parameter type is supported by Java).