Additional Handling of Jackson Parsing and Mapping Errors
Brief follow-up to Spring MVC and JSR 303 Validation error customization.
In addition to the previously shown @ExceptionHandler(MethodArgumentNotValidException.class)
method for handling JSR-303 Validation errors, we also want to handle JSON Parsing and Mapping errors by returning similarly helpful and clean JSON responses.
Jackson apparently uses two different Exception trees for the different kinds of errors we see, JsonParseException
and JsonMappingException
, both of which are wrapped in HttpMessageNotReadableException
.
Our initial attempt was to just return the getOriginalMessage()
string from both of those, along with the getPath()
information when it exists. But it turns out that at least a couple of cases we want to handle produce messages with more detail than we want to reveal. (Namely, the fully-qualified class name):
- Using a Java
enum
to limit input values to acceptable ones - Not accepting unknown input fields
So, combining all of the cases we see today, this appears to give useful, appropriate validation error responses:
/**
* Common handling of JSON parsing/mapping exceptions. Care is used to not return error
* details that would reveal internal Java package/class names.
*/
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<BaseResponseMessage> processConversionException(HttpMessageNotReadableException e) {
String msg = null;
Throwable cause = e.getCause();
if (cause instanceof JsonParseException) {
JsonParseException jpe = (JsonParseException) cause;
msg = jpe.getOriginalMessage();
}
// special case of JsonMappingException below, too much class detail in error messages
else if (cause instanceof MismatchedInputException) {
MismatchedInputException mie = (MismatchedInputException) cause;
if (mie.getPath() != null && mie.getPath().size() > 0) {
msg = "Invalid request field: " + mie.getPath().get(0).getFieldName();
}
// just in case, haven't seen this condition
else {
msg = "Invalid request message";
}
}
else if (cause instanceof JsonMappingException) {
JsonMappingException jme = (JsonMappingException) cause;
msg = jme.getOriginalMessage();
if (jme.getPath() != null && jme.getPath().size() > 0) {
msg = "Invalid request field: " + jme.getPath().get(0).getFieldName() +
": " + msg;
}
}
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(new BaseResponseMessage(CODE_ERROR_VALIDATION, msg));
}
(Where our BaseResponseMessage
is a POJO with code
and message
fields.)