Here is a problem: I have a controller that takes an input model. Lets say
public class AppUserUpdateData {
@NotNull
@Size(min = 1, max = 50)
protected String login;
@JsonDeserialize(using = MyDateTimeDeserializer.class)
protected Date startWorkDate;
*************
other properties and methods
*************
}
The problem is when I want to restrict a down board of a date I eventually get an HTTP exception 400 without any messages despite I handle this case in my code! here is a controller:
@RequestMapping(
value = "/users/{userId}", method = RequestMethod.PUT,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public @ResponseBody AbstractSuccessResult updateUser(@PathVariable Long userId,
@RequestBody AppUserUpdateData appUserUpdateRequest, HttpServletRequest request) {
AbstractSuccessResult response = new AbstractSuccessResult();
appUserService.updateUser(appUserUpdateRequest, userId);
return response;
}
Here is a Deserializer:
public class MyDateTimeDeserializer extends JsonDeserializer<Date> {
@Override
public Date deserialize(JsonParser jsonParser, DeserializationContext context)
throws IOException, JsonProcessingException {
try {
return DataTypeHelper.stringToDateTime(jsonParser.getText());
} catch (MyOwnWrittenException ex) {
throw ex;
}
}
}
In a DataTypeHelper.stringToDateTime
are some validations that are blocking invalid date-strings.
And there is a handler for a my exception:
@ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler({ MyOwnWrittenException .class})
protected ResponseEntity<Object> handleInvalidRequest(RuntimeException exc,
WebRequest request) {
MyOwnWrittenException ex = (MyOwnWrittenException) exc;
BasicErrorMessage message; = new BasicErrorMessage(ex.getMessage());
AbstractUnsuccessfulResult result = new AbstractUnsuccessfulResult(message);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return handleExceptionInternal(exc, result, headers, HttpStatus.BAD_REQUEST, request);
}
}
The problem is that when an exception in a MyDateTimeDeserializer
has been thrown it doesn't falling into a MyExceptionHandler
but I cannot understand why? What am I doing wrong?
In the response is just an empty response with a code 400(
UPD Thanks to @Joe Doe's answer the problem has been solved. Here is my updated handler:
@Order(Ordered.HIGHEST_PRECEDENCE)
@ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler({ MyOwnWrittenException .class})
protected ResponseEntity<Object> handleInvalidRequest(RuntimeException exc,
WebRequest request) {
MyOwnWrittenException ex = (MyOwnWrittenException) exc;
BasicErrorMessage message; = new BasicErrorMessage(ex.getMessage());
AbstractUnsuccessfulResult result = new AbstractUnsuccessfulResult(message);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return handleExceptionInternal(exc, result, headers, HttpStatus.BAD_REQUEST, request);
}
@Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
Throwable cause = ex.getCause();
String message = null;
if (cause instanceof JsonMappingException) {
if (cause.getCause() instanceof MyOwnWrittenException) {
return handleInvalidRequest((RuntimeException) cause.getCause(), request);
} else {
message = cause.getMessage();
}
} else {
message = ex.getMessage();
}
AbstractUnsuccessfulResult result = new AbstractUnsuccessfulResult(
new BasicErrorMessage(message));
headers.setContentType(MediaType.APPLICATION_JSON);
return handleExceptionInternal(ex, result, headers, HttpStatus.BAD_REQUEST, request);
}
}
UPD
In my project it doesn't work without annotation @Order(Ordered.HIGHEST_PRECEDENCE)
I believe that is because of number of ControllerAdvices in a project
In the following Spring Boot application we use @ControllerAdvice to handle three exceptions: when a city is not found, when there is no data, and when a data for a new city to be saved is not valid.
Exception Handler Define a class that extends the RuntimeException class. You can define the @ExceptionHandler method to handle the exceptions as shown. This method should be used for writing the Controller Advice class file. Now, use the code given below to throw the exception from the API.
Method SummaryReturn a Map with an "Allow" header. Return the HTTP method for the failed request. Return HttpHeaders with an "Allow" header. Return the list of supported HTTP methods.
You have to provide implementation to use your error handler, map the response to response entity and throw the exception. Create new error exception class with ResponseEntity field. Unfortunately xml doesn't give that level of control. You've to add message converters manually to error handler.
Before updateUser
in your controller gets invoked, its arguments have to be resolved. This is where HandlerMethodArgumentResolverComposite
comes in, and delegates to one of pre-registered HandlerMethodArgumentResolver
s - in this particular case it delegates to RequestResponseBodyMethodProcessor
.
By delegating I mean calling the resolver's resolveArgument
method. This method indirectly calls the deserialize
method from your deserializer, which throws an exception of type MyOwnWrittenException
. The problem is that this exception gets wrapped in another exception. In fact, by the time it propagates back to resolveArgument
, it's of type HttpMessageNotReadableException
.
So, rather than catching MyOwnWrittenException
in your custom exception handler, you need to catch exceptions of type HttpMessageNotReadableException
. Then, in the method that handles that case, you can check whether the "original" exception was in fact MyOwnWrittenException
- you can do that by repeatedly calling the getCause
method. In my case (it's probably going to be the same in yours), I needed to call getCause
twice to "unwrap" the original exception (HttpMessageNotReadableException
-> JsonMappingException
-> MyOwnWrittenException
).
Note that you can't simply substitute MyOwnWrittenException
with HttpMessageNotReadableException
in your exception handler since it clashes (at runtime) with another method, specifically designed to handle exceptions of the latter type, called handleHttpMessageNotReadable
.
In summary, you can do something like this:
@ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
@Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
// ex.getCause().getCause().getClass() gives MyOwnWrittenException
// the actual logic that handles the exception...
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With