I have already gone through official Spring Boot tutorial, a couple of Stack Overflow posts including this, tried @Ordered(Order=HIGHEST_PRECEDENCE)
but any exception other than the ConstraintViolationException.class
is being handled by DefaultHandlerExceptionResolver
VERSIONS
- Spring Boot - 2.5.3
- Spring - 5.3.9
PROBLEM
I want any exception in MyRestController
be handled by handlers in ControllerAdvice
defined by me so that I can manipulate the response object before sending to the client.
@Validated
@RestController
@RequestMapping(value = "/myctx", produces = MediaType.APPLICATION_JSON_VALUE)
public class MyRestController {
private static final Logger logger = LoggerFactory.getLogger(MyRestController);
@RequestMapping(
path = "/{element}",
method = RequestMethod.POST,
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ApiResponse> findAndSave(
@Allowed(values = { "a1", "a2", "a3" }) @PathVariable String element,
@ValidateMetadata @RequestBody Metadata metadata) {
...
...
}
}
The annotations @Allowed
and @ValidateMetadata
defined by me are working as expected
@ControllerAdvice
//@Order(Ordered.HIGHEST_PRECEDENCE)
public class ErrorControllerAdvice extends ResponseEntityExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(ErrorControllerAdvice.class);
@ExceptionHandler({ ConstraintViolationException.class })
public ResponseEntity<Object> handleConstraintViolationException(ConstraintViolationException ex, WebRequest request) {
ApiResponse apiResponse = ApiResponse.builder()
.timestamp(new Date())
.status(HttpStatus.BAD_REQUEST)
.message(ex.getLocalizedMessage())
.path(((ServletWebRequest) request).getRequest().getRequestURI())
//.metadata(metadata)
.build();
return handleExceptionInternal(ex, apiResponse, new HttpHeaders(), apiResponse.getStatus(), request);
}
@ExceptionHandler({ Exception.class })
public ResponseEntity<Object> handleAll(Exception ex, WebRequest request) throws Exception {
ResponseEntity<Object> tmpResponse = super.handleException(ex, request);
ResponseEntity<Object> response = new ResponseEntity<>("Response from catch-all exception handler", tmpResponse.getHeaders(), tmpResponse.getStatusCode());
return response;
}
}
At this point when I test my endpoint with a POST request and inject a ContraintViolation, my custom error response is sent to the client. However, for any other Exception, handleAll()
method in ErrorControllerAdvice
is not even invoked. For example, from the test code, I try to send a GET instead of a POST or change the URI to contain myct
instead of myctx
Why is it not working? Why do I get default error messages in case of the situations described above?
Update
I even tried throwing a NullPointerException from within the controller method but that also didn't invoke the handleAll()
method.
CodePudding user response:
Your implementation is wrong, the handleException
method will handle only those exceptions defined in the @ExceptionHandler annotation
@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
})
so, these lines of code will always throw an exception.
try {
tmpResponse = super.handleException(ex, request);
} catch (Exception handlerEx) {
logger.error(handlerEx.getMessage(), handlerEx);
}
This idea is a mistake:
Since I am extending from ResourceEntityExceptionHandler, all the exceptions were being handled by handleException() method in that class.
CodePudding user response:
The ResponseEntityExceptionHandler
handles a lot of exceptions so the handleAll
will handle only the exceptions that are not included in ResponseEntityExceptionHandler
, you can add a breakpoint in the handleException
method inside ResponseEntityExceptionHandler
to see if it is catching the exception. Another thing you can do is to throw a RuntimeException
inside findAndSave
and check if it is handle by handleAll
@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
})
CodePudding user response:
I was able to solve it for myself. I minutely went over the details again that are posted here: Setting Precedence of Multiple @ControllerAdvice @ExceptionHandlers
This is how my @ControllerAdvice bean looks like now giving me that ability to stitch in a custom ApiResponse object for any exception that the Rest Controller throws.
@RestControllerAdvice
public class ErrorControllerAdvice extends ResponseEntityExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(ErrorControllerAdvice.class);
@ExceptionHandler({ ConstraintViolationException.class })
public ResponseEntity<Object> handleConstraintViolationException(ConstraintViolationException ex, WebRequest request) {
Metadata metadata = getRequestBody(request);
ApiResponse apiResponse = new ApiResponse(); // details omitted
return handleExceptionInternal(ex, apiResponse, new HttpHeaders(), apiResponse.getStatus(), request);
}
@ExceptionHandler({ Exception.class })
public ResponseEntity<Object> handleAll(Exception ex, WebRequest request) {
ResponseEntity<Object> tmpResponse = null;
String message = null;
try {
tmpResponse = super.handleException(ex, request);
} catch (Exception handlerEx) {
logger.error(handlerEx.getMessage(), handlerEx);
}
HttpHeaders httpHeaders = tmpResponse == null ? new HttpHeaders() : tmpResponse.getHeaders();
HttpStatus httpStatus = tmpResponse == null ? HttpStatus.INTERNAL_SERVER_ERROR : tmpResponse.getStatusCode();
ApiResponse apiResponse = ApiResponse.builder()
.timestamp(new Date())
.status(httpStatus)
.message(ex.getLocalizedMessage()) // ex and handlerEx will have same content because of line 98 in ResponseEntityExceptionHandler
.path(((ServletWebRequest) request).getRequest().getRequestURI())
.build();
return new ResponseEntity<>(apiResponse, httpHeaders, httpStatus);
}
}