@RequestBody @Valid SomeDTO has field of enum type, custom error message
Solution 1
@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
Solution 2
You do not need @Valid for enum validation, you can achieve the required response using below code:
Controller Code, StackDTO has an enum PaymentType in it:
@RequestMapping(value = "/reviews", method = RequestMethod.POST)
@ResponseBody
public ResponseEntity<String> add(@RequestBody StackDTO review) {
return new ResponseEntity<String>(HttpStatus.ACCEPTED);
}
Create an exception class, as EnumValidationException
public class EnumValidationException extends Exception {
private String enumValue = null;
private String enumName = null;
public String getEnumValue() {
return enumValue;
}
public void setEnumValue(String enumValue) {
this.enumValue = enumValue;
}
public String getEnumName() {
return enumName;
}
public void setEnumName(String enumName) {
this.enumName = enumName;
}
public EnumValidationException(String enumValue, String enumName) {
super(enumValue);
this.enumValue = enumValue;
this.enumName = enumName;
}
public EnumValidationException(String enumValue, String enumName, Throwable cause) {
super(enumValue, cause);
this.enumValue = enumValue;
this.enumName = enumName;
}
}
I have enum as below, with a special annotation @JsonCreator on a method create
public enum PaymentType {
CREDIT("Credit"), DEBIT("Debit");
private final String type;
PaymentType(String type) {
this.type = type;
}
String getType() {
return type;
}
@Override
public String toString() {
return type;
}
@JsonCreator
public static PaymentType create (String value) throws EnumValidationException {
if(value == null) {
throw new EnumValidationException(value, "PaymentType");
}
for(PaymentType v : values()) {
if(value.equals(v.getType())) {
return v;
}
}
throw new EnumValidationException(value, "PaymentType");
}
}
Finally RestErrorHandler class,
@ControllerAdvice
public class RestErrorHandler {
@ExceptionHandler(HttpMessageNotReadableException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public ResponseEntity<ValidationErrorDTO> processValidationIllegalError(HttpMessageNotReadableException ex,
HandlerMethod handlerMethod, WebRequest webRequest) {
EnumValidationException exception = (EnumValidationException) ex.getMostSpecificCause();
ValidationErrorDTO errorDTO = new ValidationErrorDTO();
errorDTO.setEnumName(exception.getEnumName());
errorDTO.setEnumValue(exception.getEnumValue());
errorDTO.setErrorMessage(exception.getEnumValue() + " is an invalid " + exception.getEnumName());
return new ResponseEntity<ValidationErrorDTO>(errorDTO, HttpStatus.BAD_REQUEST);
}
}
ValidationErrorDTO is the dto with setters/getters of enumValue, enumName and errorMessage. Now when you send POST call to controller endpoint /reviews with below request
{"paymentType":"Credit2"}
Then code returns response as 400 with below response body -
{
"enumValue": "Credit2",
"enumName": "PaymentType",
"errorMessage": "Credit2 is an invalid PaymentType"
}
Let me know if it resolves your issue.
Solution 3
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. 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]."
}
Solution 4
Yon can achieve this using @ControllerAdvice
as follows
@org.springframework.web.bind.annotation.ExceptionHandler(value = {InvalidFormatException.class})
public ResponseEntity handleIllegalArgumentException(InvalidFormatException exception) {
return ResponseEntity.badRequest().body(exception.getMessage());
}
Basically , the idea is to catch com.fasterxml.jackson.databind.exc.InvalidFormatException
and handle it as per your requirement.
Related videos on Youtube
timpham
Updated on October 13, 2022Comments
-
timpham over 1 year
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".
-
timpham over 6 years@AmitKBist this doesn't answer the question on enum type
-
-
Arun Gowda about 3 yearsI like the idea. But IMHO, it's not scalable. If there are 100 enums, I don't want to write
@JsonCreator
for all those enums. -
Arun Gowda about 3 yearsCan we make it more specific to Enums?
HttpMessageNotReadableException
is a very broad exception and will cover a lot of other exceptions like invalid json