How to catch HTTP 405 "Method Not Allowed" exception using #ExceptionHandler? - java

I created a REST application and added a class to handle exceptions:
#RestControllerAdvice(basePackages = "com.foxminded.university.api.controller")
public class ApiGlobalExceptionHandler extends ResponseEntityExceptionHandler {
private static final String API_UNHANDLED_EXCEPTION = "REST API reached unhandled exception: %s";
private static final HttpStatus internalServerError = HttpStatus.INTERNAL_SERVER_ERROR;
#ExceptionHandler(Exception.class)
public ResponseEntity<Object> handle(Exception e) {
ExceptionDetail exceptionDetail = new ExceptionDetail(
String.format(API_UNHANDLED_EXCEPTION, e.getMessage()),
internalServerError,
ZonedDateTime.now(ZoneId.systemDefault()));
return new ResponseEntity<Object>(exceptionDetail, internalServerError);
}
#Override
public ResponseEntity<Object> handleTypeMismatch(
TypeMismatchException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
ExceptionDetail exceptionDetail = new ExceptionDetail(
String.format(API_UNHANDLED_EXCEPTION, ex.getMessage()),
internalServerError,
ZonedDateTime.now(ZoneId.systemDefault()));
return new ResponseEntity<Object>(exceptionDetail, internalServerError);
}
#Override
public ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException e, HttpHeaders headers,
HttpStatus status, WebRequest request) {
ExceptionDetail exceptionDetail = new ExceptionDetail(
String.format(API_UNHANDLED_EXCEPTION, e.getMessage()),
internalServerError,
ZonedDateTime.now(ZoneId.systemDefault()),
e.getBindingResult().getFieldErrors());
return new ResponseEntity<Object>(exceptionDetail, internalServerError);
}
}
When I send the wrong post request via postman http://localhost:8080/api/groups/jkjk instead of http://localhost:8080/api/groups
It throws me an exception that I can't catch initializing when debugging, neither in the ApiGlobalExceptionHandler class nor in the ResponseEntityExceptionHandler class:
{
"timestamp": 1604171144423,
"status": 405,
"error": "Method Not Allowed",
"message": "",
"path": "/api/groups/jkjk"
}
All other exceptions I can catch. How do I catch this exception to add custom handling?

You just need to add a new method to with MethodNotAllowedException in its signature.
#ExceptionHandler(value = MethodNotAllowedException.class)
public ResponseEntity<Object> handleMethodNotAllowedExceptionException(MethodNotAllowedException ex) {
return buildResponseEntity(HttpStatus.METHOD_NOT_ALLOWED, null, null, ex.getMessage(), null);
}
private ResponseEntity<Object> buildResponseEntity(HttpStatus status, HttpHeaders headers, Integer internalCode, String message, List<Object> errors) {
ResponseBase response = new ResponseBase() //A generic ResponseBase class
.success(false)
.message(message)
.resultCode(internalCode != null ? internalCode : status.value())
.errors(errors != null
? errors.stream().filter(Objects::nonNull).map(Objects::toString).collect(Collectors.toList())
: null);
return new ResponseEntity<>((Object) response, headers, status);
}
You can customize the buildResponseEntityas you please.
UPDATED
I revisited my answer since it didn't meet your requirement. So, it follows like this:
I send post request for a method that accepts GET. This will fire Request method 'POST' not supported as printed below.
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.Looking up handler method for path /v1/user/profile/1
org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver.Resolving exception from handler [null]: org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported
org.springframework.beans.factory.support.DefaultListableBeanFactory.Returning cached instance of singleton bean 'ethGlobalExceptionHandler'
In this case, there is no need to add
#ExceptionHandler(value = HttpRequestMethodNotSupportedException.class).
In fact if you do so, the following error will be thrown (since it is already handled),
java.lang.IllegalStateException: Ambiguous #ExceptionHandler method mapped for [class org.springframework.web.HttpRequestMethodNotSupportedException]:....
So, the solution will be:
#Override
protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
return buildResponseEntity(HttpStatus.METHOD_NOT_ALLOWED, headers, null, ex.getMessage(), Arrays.asList(""));
}

I found the explanation here Custom handling for 405 error with Spring Web MVC
It says The reason your #ExceptionHandler annotated method never catches your exception is because these ExceptionHandler annotated methods are invoked after a successful Spring controller handler mapping is found. However, your exception is raised before that.
And the solution is to extends not from ResponseEntityExceptionHandler class, but from DefaultHandlerExceptionResolver and override it handleHttpRequestMethodNotSupported method.

Related

How to log exceptions in Controller Advice when exceptions extends to ResponseStatusException?

I am unable to capture exceptions in controller advice and log those exceptions. The exceptions are extended to ResponseStatusException. I tried this out but it didnt work.
#ControllerAdvice
public class ExceptionHandler extends ResponseEntityExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionHandler.class);
#Override
protected ResponseEntity<Object> handleExceptionInternal(
#NonNull Exception ex,
#Nullable Object body,
#NonNull HttpHeaders headers,
HttpStatus status,
#NonNull WebRequest request) {
if (status.is5xxServerError()) {
LOGGER.error("An exception occurred, which will cause a {} response", status, ex);
} else if (status.is4xxClientError()) {
LOGGER.warn("An exception occured, which will cause a {} response", status, ex);
} else {
LOGGER.debug("An exception occured, which will cause a {} response", status, ex);
}
return super.handleExceptionInternal(ex, body, headers, status, request);
}
}
How could I achieve this requirement?
I recommend not extending from ResponseEntityExceptionHandler as a first test.
Create an exception handler by doing something like this:
#ExceptionHandler(ResponseStatusException.class)
public ResponseEntity<Object> testHandler(Exception ex, WebRequest request) {
return new ResponseEntity<>("whatever", HttpStatus.NOT_FOUND);
}
I suspect that you are not telling spring to handle this specific exception via:
#ExceptionHandler(ResponseStatusException.class)

How can I access the HTTP response content of a Spring Boot default error response?

I have a Spring Boot application that is capturing the HTTP response content of REST requests made to the application. I am doing this for the purposes of logging and future analysis of the requests entering the system.
Currently, I have this implemented as a filter bean (per OncePerRequestFilter), using a ContentCachingResponseWrapper to capture the content written to the output stream:
#Component
public class ResponseBodyLoggingFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
ContentCachingResponseWrapper cachingResponse =
new ContentCachingResponseWrapper(response);
try {
filterChain.doFilter(request, cachingResponse);
} finally {
byte[] responseBytes = cachingResponse.getContentInputStream().readAllBytes();
System.out.println("Response: \"" + new String(responseBytes) + "\"");
cachingResponse.copyBodyToResponse();
}
}
}
This works for the majority of requests to the application. One thing it does not capture, however, is the default Spring Boot error response. Rather than capturing the content of the response, it is instead returning the empty string.
build.gradle
plugins {
id 'org.springframework.boot' version '2.5.3'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
sourceCompatibility = '11'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
}
Controller:
#RestController
#RequestMapping("/test")
public class TestController {
#GetMapping("/404")
public void throw404() {
throw new ResponseStatusException(BAD_REQUEST);
}
}
HTTP response:
{
"timestamp": "2021-08-03T18:30:18.934+00:00",
"status": 400,
"error": "Bad Request",
"path": "/test/404"
}
System output:
Response: ""
I've confirmed that if I switch from the Spring Boot default of embedded Tomcat to embedded Jetty (using spring-boot-starter-jetty and excluding spring-boot-starter-tomcat), this issue still occurs.
How can I capture the Spring Boot error response output within my application? Note that I do not need this to be a filter if another solution solves the problem.
I have not yet determined a good way to achieve the stated goal of getting the Spring Boot error response body within the filter, but after some debugging and diving into the internals of Spring, I believe I may have determined why it isn't working, at least.
It looks like BasicErrorController.error(HttpServletRequest request) is the part of the framework responsible for returning the error object to be rendered.
However, observing where this controller method is called, it appears as if it is happening during the call to Servlet.service() after the actual filtering has taken place. Per tomcat-embed-core's ApplicationFilterChain:
private void internalDoFilter(ServletRequest request,
ServletResponse response)
throws IOException, ServletException {
// Call the next filter if there is one
// [...]
Filter filter = filterConfig.getFilter();
// [...]
filter.doFilter(request, response, this);
// [...]
// We fell off the end of the chain -- call the servlet instance
// [...]
servlet.service(request, response);
// [...]
}
Per the above code, the ResponseBodyLoggingFilter filter is called in filter.doFilter(request, response, this), but BasicErrorController.error(...) is not called until afterward by servlet.service(request, response).
I am aware that your question is related with response logging but when it comes to error handling, please, consider the following approach as well, it can complement your code when an error occurs.
As described in the Spring Boot documentation when describing error handling, probably the way to go will be to define a #ControllerAdvice that extends ResponseEntityExceptionHandler and apply it to the controllers you need to.
You can define ExceptionHandlers for your custom exception or override the methods already provided in ResponseEntityExceptionHandler.
For example, you can override the main handleException method :
// will apply for all controllers. The annotation provides attributes for limiting that scope
#ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
#Override
#ExceptionHandler({
HttpRequestMethodNotSupportedException.class,
HttpMediaTypeNotSupportedException.class,
HttpMediaTypeNotAcceptableException.class,
MissingPathVariableException.class,
MissingServletRequestParameterException.class,
ServletRequestBindingException.class,
ConversionNotSupportedException.class,
TypeMismatchException.class,
HttpMessageNotReadableException.class,
HttpMessageNotWritableException.class,
MethodArgumentNotValidException.class,
MissingServletRequestPartException.class,
BindException.class,
NoHandlerFoundException.class,
AsyncRequestTimeoutException.class
})
#Nullable
public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) throws Exception {
// Log errors, obtain the required information,...
// ...
return super.handleException(ex, request);
}
}
At the moment, this is the default implementation provided by this method:
/**
* Provides handling for standard Spring MVC exceptions.
* #param ex the target exception
* #param request the current request
*/
#ExceptionHandler({
HttpRequestMethodNotSupportedException.class,
HttpMediaTypeNotSupportedException.class,
HttpMediaTypeNotAcceptableException.class,
MissingPathVariableException.class,
MissingServletRequestParameterException.class,
ServletRequestBindingException.class,
ConversionNotSupportedException.class,
TypeMismatchException.class,
HttpMessageNotReadableException.class,
HttpMessageNotWritableException.class,
MethodArgumentNotValidException.class,
MissingServletRequestPartException.class,
BindException.class,
NoHandlerFoundException.class,
AsyncRequestTimeoutException.class
})
#Nullable
public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) throws Exception {
HttpHeaders headers = new HttpHeaders();
if (ex instanceof HttpRequestMethodNotSupportedException) {
HttpStatus status = HttpStatus.METHOD_NOT_ALLOWED;
return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, headers, status, request);
}
else if (ex instanceof HttpMediaTypeNotSupportedException) {
HttpStatus status = HttpStatus.UNSUPPORTED_MEDIA_TYPE;
return handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, headers, status, request);
}
else if (ex instanceof HttpMediaTypeNotAcceptableException) {
HttpStatus status = HttpStatus.NOT_ACCEPTABLE;
return handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException) ex, headers, status, request);
}
else if (ex instanceof MissingPathVariableException) {
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
return handleMissingPathVariable((MissingPathVariableException) ex, headers, status, request);
}
else if (ex instanceof MissingServletRequestParameterException) {
HttpStatus status = HttpStatus.BAD_REQUEST;
return handleMissingServletRequestParameter((MissingServletRequestParameterException) ex, headers, status, request);
}
else if (ex instanceof ServletRequestBindingException) {
HttpStatus status = HttpStatus.BAD_REQUEST;
return handleServletRequestBindingException((ServletRequestBindingException) ex, headers, status, request);
}
else if (ex instanceof ConversionNotSupportedException) {
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
return handleConversionNotSupported((ConversionNotSupportedException) ex, headers, status, request);
}
else if (ex instanceof TypeMismatchException) {
HttpStatus status = HttpStatus.BAD_REQUEST;
return handleTypeMismatch((TypeMismatchException) ex, headers, status, request);
}
else if (ex instanceof HttpMessageNotReadableException) {
HttpStatus status = HttpStatus.BAD_REQUEST;
return handleHttpMessageNotReadable((HttpMessageNotReadableException) ex, headers, status, request);
}
else if (ex instanceof HttpMessageNotWritableException) {
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
return handleHttpMessageNotWritable((HttpMessageNotWritableException) ex, headers, status, request);
}
else if (ex instanceof MethodArgumentNotValidException) {
HttpStatus status = HttpStatus.BAD_REQUEST;
return handleMethodArgumentNotValid((MethodArgumentNotValidException) ex, headers, status, request);
}
else if (ex instanceof MissingServletRequestPartException) {
HttpStatus status = HttpStatus.BAD_REQUEST;
return handleMissingServletRequestPart((MissingServletRequestPartException) ex, headers, status, request);
}
else if (ex instanceof BindException) {
HttpStatus status = HttpStatus.BAD_REQUEST;
return handleBindException((BindException) ex, headers, status, request);
}
else if (ex instanceof NoHandlerFoundException) {
HttpStatus status = HttpStatus.NOT_FOUND;
return handleNoHandlerFoundException((NoHandlerFoundException) ex, headers, status, request);
}
else if (ex instanceof AsyncRequestTimeoutException) {
HttpStatus status = HttpStatus.SERVICE_UNAVAILABLE;
return handleAsyncRequestTimeoutException((AsyncRequestTimeoutException) ex, headers, status, request);
}
else {
// Unknown exception, typically a wrapper with a common MVC exception as cause
// (since #ExceptionHandler type declarations also match first-level causes):
// We only deal with top-level MVC exceptions here, so let's rethrow the given
// exception for further processing through the HandlerExceptionResolver chain.
throw ex;
}
}
This SO question can be valuable as well.
This other SO question provides different and alternative approaches that could be interesting, for instance, by extending DispatcherServlet.
Depending on your actual use case, perhaps the httptrace actuator may also meet your requirements by enabling http tracing.

How to override handleMethodArgumentNotValid properly in Spring Boot

I'm trying to override the handleMethodArgumentNotValid method. But I'm still getting the error:
Caused by: java.lang.IllegalStateException: Ambiguous #ExceptionHandler method mapped for [class org.springframework.web.bind.MethodArgumentNotValidException]
I've overridden the method as suggested in various posts (for example in Spring Rest ErrorHandling #ControllerAdvice / #Valid) like this:
#Order(Ordered.HIGHEST_PRECEDENCE)
#RestControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
#Override
#ExceptionHandler(value = MethodArgumentNotValidException.class)
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest webRequest) {
String message = errorMessageBuilder(ex);
return handleExceptionInternal(ex, message, new HttpHeaders(), HttpStatus.UNPROCESSABLE_ENTITY, webRequest);
}
}
What am I doing wrong?
Sincerely,
Marcel
If you want to create your own response, then try with the below code.
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(MethodArgumentNotValidException.class)
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex) {
Map<String, Object> body = new HashMap<>();
body.put("error", ex);
return new ResponseEntity<>(body, HttpStatus.UNPROCESSABLE_ENTITY);
}
}
You can change the Map to your customize object and set the error pieces of information you want. I hope it will work.

Spring boot Rest responding with empty body for exceptions other than the ones overridden in my #ControllerAdvice

I have a #ControllerAdvice extending ResponseEntityExceptionHandler as an attempt for me to control standard response for any exception raised with in the API call workflow.
Without the Controller advice. I get HTML based generic response generated by spring with correct response headers. But when I add my #ControllerAdvice, Spring doesn't response with generic error body. The body is empty with correct response headers
#Override
protected ResponseEntity<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
String erroMessage = "Required Parameter: '"+ex.getParameterName()+"' was not available in the request.";
TrsApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, erroMessage, ex, ApiErrorCode.INVALID_REQUEST);
return buildResponseEntity(apiError);
}
So, now in case of a required parameter missing in the request, the flow beautifully trigger my overridden implementation and responds with JSON payload describing the error. But, in case of any other exception like HttpMediaTypeNotAcceptableException, spring is responding with empty body.
Before I added my advice, spring was responding with generic error response. I am new to spring boot ecosystem. Need help in understanding if this is an expected behavior of if there is a better approach of achieving centralized error handling.
I guess that I found out a solution for swallowed body when ControllerAdvice class is extending ResponeEntityExceptionHandler. In my case the setup looks like that:
#ControllerAdvice
#Slf4j
class GlobalExceptionHandlers extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException exception,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
// logic that creates apiError object (object with status, message, errorCode, etc)
//...
return handleExceptionInternal(exception, apiError, headers, status, request);
}
And this worked like a charm for exceptions of class MethodArgumentNotValidException. But it broke all other exceptions handled by ResponseEntityExceptionHandler, and returned empty response body for them.
But the fix is easy, just override handleExceptionInternal from ResponseEntityExceptionHandler:
#ControllerAdvice
#Slf4j
class GlobalExceptionHandlers extends ResponseEntityExceptionHandler {
/// ... code from previous snippet
#Override
protected ResponseEntity<Object> handleExceptionInternal(
Exception exception,
Object body,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
// for all exceptions that are not overriden, the body is null, so we can
// just provide new body based on error message and call super method
var apiError = Objects.isNull(body)
? new ApiError(status, exception.getMessage()) // <--
: body;
return super.handleExceptionInternal(exception, apiError, headers, status, request);
}
}
This is expected behavior.Look at the source code of the class ResponseEntityExceptionHandler.
#ExceptionHandler({
org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException.class,
HttpRequestMethodNotSupportedException.class,
HttpMediaTypeNotSupportedException.class,
HttpMediaTypeNotAcceptableException.class,
MissingPathVariableException.class,
MissingServletRequestParameterException.class,
ServletRequestBindingException.class,
ConversionNotSupportedException.class,
TypeMismatchException.class,
HttpMessageNotReadableException.class,
HttpMessageNotWritableException.class,
MethodArgumentNotValidException.class,
MissingServletRequestPartException.class,
BindException.class,
NoHandlerFoundException.class,
AsyncRequestTimeoutException.class
})
public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) {
All of these exceptions are handled WITHOUT the response body.
A common method is invoked :
//second parameter is body which is null
handleExceptionInternal(ex, null, headers, status, request)
If you need to handle specific exceptions differently, override them, example where I wanted to send a custom response for HttpMessageNotReadableException
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex,
HttpHeaders headers, HttpStatus status, WebRequest request)
{
logger.error("handleHttpMessageNotReadable()", ex);
ValidationErrors validationErrors = null;
if (ex.getRootCause() instanceof InvalidFormatException) {
InvalidFormatException jacksonDataBindInvalidFormatException = (InvalidFormatException) ex.getRootCause();
validationErrors = new ValidationErrors(jacksonDataBindInvalidFormatException.getOriginalMessage());
}
headers.add("X-Validation-Failure", "Request validation failed !");
return handleExceptionInternal(ex, validationErrors, headers, status, request);
}
You need to define generic exception structure once you use #ControllerAdvice.
#ResponseBody
#ExceptionHandler(Exception.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse generationExceptionHandler(Exception e){
log.info("Responding INTERNAL SERVER ERROR Exception");
return new ErrorResponse(ServiceException.getSystemError());
}

Catch a deserialization exception before a ControllerAdvice

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
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 HandlerMethodArgumentResolvers - 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...
}
}

Categories

Resources