Search code examples
javavalidationspring-bootspring-validator

javax.validation.UnexpectedTypeException using Spring ConstraintValidator for an enumeration


I have implemented a ConstraintValidator in order to valide a DTO that contains an enumeration. I followed this Spring documentation for that.

This is the custom annotation to be applied to the enum field:

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

  String message() default "{com.test.validation.constraints.ValidEnum}";

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

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

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

}

The EnumValidator looks like this:

public class EnumValidator implements ConstraintValidator<ValidEnum,String> {

      private Set<String> enumValues;

      @Override
      public void initialize(ValidEnum targetEnum) {
          Class<? extends Enum> enumSelected = targetEnum.targetClassType();
          enumValues = (Set<String>) EnumSet.allOf(enumSelected).stream()
              .map(e -> ((Enum<? extends Enum<?>>) e).name()).collect(Collectors
                  .toSet());
      }

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

This is the enum:

public enum Gender {
  MALE,FEMALE;
}

This is the DTO to validate:

public final class UserDTO{

  @ValidEnum(target = Gender.class)
  private Gender gender;

  @NotEmpty
  @Max(100)
  private String fullName;
}

And the controller that is validating the field:

@Controller
public class RegistrationController {

  private static final String REGISTER_USER = "/register";

  private final RegistrationService registrationService;


  @PostMapping(value = REGISTER_USER)
  @Consumes(APPLICATION_JSON)
  @Produces(APPLICATION_JSON)
  public UserRegistrationResponse register(@Valid UserDTO userRegistrationRequest) {

    return registrationService.register(userRegistrationRequest);
  }

}

It seems that Spring is not detecting the validator, because it throws this exception:

org.springframework.web.util.NestedServletException: Request processing failed; 
nested exception is javax.validation.UnexpectedTypeException: HV000030: 

No validator could be found for constraint 'com.test.ws.web.validation.ValidEnumType' validating type 'com.test.ws.domain.model.Gender'. Check configuration for 'gender'

I am using Spring boot 2.0.4 which include the required dependencies for validation.

Any idea why it fails?


Solution

  • You have three mistakes in your code.

    1. @Max(100) this annotation shouldn't be applied to String field! only for numeric type. If you need to specify String size range restrictions you may use @Size(min = 2, max = 250) annotation.
    2. You forgot RequestBody annotation in the controller method signature:

    register(@Valid @RequestBody UserDTO userRegistrationRequest)

    1. Enum validation consume String value from Rest API, not Enum itself. That's why you've got an error here.

    I've created test project for you. Please check it here https://github.com/alex-petrov81/stackoverflow-answers/tree/master/enum-validator