Search code examples
springerror-handlingenumsjacksonjson-deserialization

Jackson deserialization errorhandling in spring-framework


I'm looking for a clean way to handle Jackson Deserialization errors for REST web requests.

More precisely: I have an Enum in a incoming DTO object, mapped from JSON. But if the user sends a wrong value, a 400 Bad Request is returned. I would like to return a 422 Unprocessable Entity with a correct message.

One option would be to accept a String, and use bean validation. However, it's not possible to pass all enum values as a list to the annotation (not a constant), so I would need to pass all enum values separately and keep them up to date. This will be very error prone over the whole application. I'm looking for a more structural way to handle this.


Solution

  • I solved this by using a String in the DTO and using a public @interface EnumValueas annotation.

    The EnumValue:

    @ReportAsSingleViolation
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Constraint(validatedBy = EnumValueValidator.class)
    @Target(ElementType.FIELD)
    public @interface EnumValue {
        Class<? extends Enum> value();
        String message() default "The input contains validation errors.";
        Class<?>[] groups() default { };
        Class<? extends Payload>[] payload() default { };
    }
    

    The validator:

    public class EnumValueValidator implements ConstraintValidator<EnumValue, String> {
    
        private Class<? extends Enum> enumClass;
        private String message;
    
        @Override
        public void initialize(final EnumValue constraintAnnotation) {
            this.enumClass = constraintAnnotation.value();
            this.message = constraintAnnotation.message();
        }
    
        @Override
        public boolean isValid(final String value, final ConstraintValidatorContext context) {
            boolean valid = false;
            for (final Enum enumValue : enumClass.getEnumConstants()) {
                if (enumValue.name().equals(value)) {
                    valid = true;
                }
            }
            if (!valid) {
                context.buildConstraintViolationWithTemplate(message) //
                       .addConstraintViolation();
            }
            return valid;
        }
    }