Search code examples
javaspring-restcontrollerhibernate-validatorjavax-validationjakarta-validation

Dynamic Validation message in Constraint Validation of Enums


I was trying to Validate the Enum before Request Processing,

EnumValidator.java

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EnumValidatorImpl.class)
public @interface EnumValidator {

    Class<? extends Enum<?>> enumClass();

    String message() default "value not valid {enumClass}";

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

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

}

EnumValidatorImpl.java

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.List;
import java.util.stream.Stream;

public class EnumValidatorImpl implements ConstraintValidator<EnumValidator, Enum<?>> {

    private List<String> allowableValues;

    @Override
    public void initialize(EnumValidator constraintAnnotation) {
        allowableValues = Stream.of(constraintAnnotation.enumClass()
                .getEnumConstants())
                .map(Enum::name)
                .toList();
    }

    @Override
    public boolean isValid(Enum<?> anEnum, ConstraintValidatorContext constraintValidatorContext) {
        return anEnum != null && allowableValues.contains(anEnum.name());
    }
}

Person.java

import lombok.Data;

@Data
public class Person {

    @EnumValidator(enumClass = Status.class)
    private String status;
}

Status.java

public enum Status {

    ACTIVE,
    IN_ACTIVE
}

It was working fine in-terms of validation, Right now the message comes as value not valid emumclass package name. Is there any to display all the members of enum in error message in this case ACTIVE, IN_ACTIVE


Solution

  • Since you are using the Hibernate Validator you can cast the context in the validator to HibernateConstraintValidatorContext, and that would allow you to add additional message parameters:

    public class EnumValidatorImpl implements ConstraintValidator<EnumValidator, Enum<?>> {
    
        private List<String> allowableValues;
    
        @Override
        public void initialize(EnumValidator constraintAnnotation) {
            allowableValues = Stream.of(constraintAnnotation.enumClass()
                            .getEnumConstants())
                    .map(Enum::name)
                    .toList();
        }
    
        @Override
        public boolean isValid(Enum<?> anEnum, ConstraintValidatorContext constraintValidatorContext) {
            if ( constraintValidatorContext instanceof HibernateConstraintValidatorContext ) {
                ( (HibernateConstraintValidatorContext) constraintValidatorContext ).addMessageParameter(
                        "enumConstants", allowableValues );
            }
            return anEnum != null && allowableValues.contains(anEnum.name());
        }
    }
    

    and then use these message parameters in the message template:

    @Target({ElementType.FIELD, ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Constraint(validatedBy = EnumValidatorImpl.class)
    public @interface EnumValidator {
    
        Class<? extends Enum<?>> enumClass();
    
        String message() default "value not valid {enumClass}. Available options: {enumConstants}";
    
        Class<?>[] groups() default {};
    
        Class<? extends Payload>[] payload() default {};
    
    }