Search code examples
springspring-bootspring-mvcjava-8annotations

no validator found for custom annotation and validation for enum springboot java8


Getting this error HV000030: No validator could be found for constraint EnumChecker validating type MyEnum Check configuration for ‘myenum’.

Using the annotation on this class to be validated, I can't do private final String myenum because I need to use it as an enum in order to use the function inside the enum

public class Request {
     @EnumChecker(enumclass = MyEnum.class)
     private final MyEnum myenum;
}

annotation

@Documented
@Constraint(validatedBy = MyEnumValidator.class)
@Target({FIELD, PARAMETER, TYPE, TYPE_USE, TYPE_PARAMETER, METHOD, CONSTRUCTOR, ANNOTATION_TYPE})
@Retention(RUNTIME)
public @interface EnumChecker {
  String message() default "must be one of these values";
  Class<?>[] groups() default {};
  Class<? extends Payload>[] payload() default {};
  Class<? extends Enum> enumClass();
}

validator

public class MyEnumValidator implements ConstraintValidator<EnumChecker, String> {
  private List<String> validEnumList;

  @Override
  public void initialize(EnumChecker constraintAnnotation) {
    validEnumList = Arrays.stream(constraintAnnotation.enumClass().getEnumConstants())
        .map(Enum::name)
        .collect(Collectors.toList());
  }

  @Override
  public boolean isValid(String value, ConstraintValidatorContext context) {
    return validEnumList.contains(value);
  }
}

enum

public enum MyEnum {

  YES, NO;

  private static final Map<MyEnum, String> MyEnumConverter =
      Map.of(YES, "yes");

  public String toMyString() {
    return MyEnumConverter.get(this);
  }

}


Solution

  • First delete final modifier in myenum and create setter and getter for that field, because Jackson is not able to bind values from http request to your Request class for example.

    public class Request {
      @EnumChecker(enumClass = MyEnum.class)
      private MyEnum myenum;
      
      public MyEnum getMyenum() {
        return myenum;
      }
      
      public void setMyenum(MyEnum myenum) {
        this.myenum = myenum;
      }
    }
    

    Then change ConstraintValidator target type to match MyEnum instead of String

    public class MyEnumValidator implements ConstraintValidator<EnumChecker, MyEnum> {
    
      private List<MyEnum> validEnumList;
    
      @Override
      public void initialize(EnumChecker constraintAnnotation) {
        validEnumList = Arrays.stream(constraintAnnotation.enumClass().getEnumConstants())
            .collect(Collectors.toList());
      }
      @Override
      public boolean isValid(MyEnum value, ConstraintValidatorContext context) {
        return validEnumList.contains(value);
      }
    }
    

    Test: I did the following test

    @RestController
    public class MainController {
    
      @PostMapping("/annot")
      public String filter(@RequestBody @Valid Request req) {
        return req.getMyenum().name();
      }
    }
    

    Also I changed the implementation of MyEnumValidator to only see validation effect and prove that it takes place. (I admit that currently this validator does not make sense. Also using only @RequestBody guarantees that myenum can only Have YES or NO values otherwise throws exception)

    public class MyEnumValidator implements ConstraintValidator<EnumChecker, MyEnum> {
    
      private List<MyEnum> validEnumList;
    
      @Override
      public void initialize(EnumChecker constraintAnnotation) {
        validEnumList = Arrays.stream(constraintAnnotation.enumClass().getEnumConstants())
            .collect(Collectors.toList());
      }
      @Override
      public boolean isValid(MyEnum value, ConstraintValidatorContext context) {
        return validEnumList.contains(value) && value.name().length() == 3;
      }
    }
    

    enter image description here

    For a invalid case:

    enter image description here

    stacktrace:

    Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public java.lang.String api.sweater.controller.MainController.filter(api.sweater.request.Request): [Field error in object 'request' on field 'myenum': rejected value [NO]; codes [EnumChecker.request.myenum,EnumChecker.myenum,EnumChecker.api.sweater.request.MyEnum,EnumChecker]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [request.myenum,myenum]; arguments []; default message [myenum],class api.sweater.request.MyEnum]; default message [must be one of these values]] ]