Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

@RequestBody @Valid SomeDTO has field of enum type, custom error message

I have the following @RestController

@RequestMapping(...)
public ResponseEntity(@RequestBody @Valid SomeDTO, BindingResult errors) {
//do something with errors if validation error occur
}

public class SomeDTO {
   public SomeEnum someEnum;
}

If the JSON request is { "someEnum": "valid value" }, everything works fine. However, if the request is { "someEnum": "invalid value" }, it only return error code 400.

How can I trap this error so I can provide a custom error message, such as "someEnum must be of value A/B/C".

like image 594
timpham Avatar asked Aug 30 '17 19:08

timpham


2 Answers

The answer provided by @Amit is good and works. You can go ahead with that if you want to deserialize an enum in a specific way. But that solution is not scalable. Because every enum which needs validation must be annotated with @JsonCreator.

Other answers won't help you beautify the error message.

So here's my solution generic to all the enums in spring web environment.

@RestControllerAdvice
public class ControllerErrorHandler extends ResponseEntityExceptionHandler {
    public static final String BAD_REQUEST = "BAD_REQUEST";
    @Override
    public ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException exception,
                                                               HttpHeaders headers, HttpStatus status, WebRequest request) {
        String genericMessage = "Unacceptable JSON " + exception.getMessage();
        String errorDetails = genericMessage;

        if (exception.getCause() instanceof InvalidFormatException) {
            InvalidFormatException ifx = (InvalidFormatException) exception.getCause();
            if (ifx.getTargetType()!=null && ifx.getTargetType().isEnum()) {
                errorDetails = String.format("Invalid enum value: '%s' for the field: '%s'. The value must be one of: %s.",
                        ifx.getValue(), ifx.getPath().get(ifx.getPath().size()-1).getFieldName(), Arrays.toString(ifx.getTargetType().getEnumConstants()));
            }
        }
        ErrorResponse errorResponse = new ErrorResponse();
        errorResponse.setTitle(BAD_REQUEST);
        errorResponse.setDetail(errorDetails);
        return handleExceptionInternal(exception, errorResponse, headers, HttpStatus.BAD_REQUEST, request);
    }

}

This will handle all the invalid enum values of all types and provides a better error message for the end user.

Sample output:

{
    "title": "BAD_REQUEST",
    "detail": "Invalid enum value: 'INTERNET_BANKING' for the field: 'paymentType'. The value must be one of: [DEBIT, CREDIT]."
}
like image 86
Arun Gowda Avatar answered Sep 24 '22 00:09

Arun Gowda


@ControllerAdvice
public static class GenericExceptionHandlers extends ResponseEntityExceptionHandler {

    @Override
    protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException e, HttpHeaders headers, HttpStatus status, WebRequest request) {
        return new ResponseEntity<>(new ErrorDTO().setError(e.getMessage()), HttpStatus.BAD_REQUEST);
    }
}

I created a fully functional Spring boot Application with a Test on Bitbucket

like image 36
Klaus Groenbaek Avatar answered Sep 22 '22 00:09

Klaus Groenbaek