In my #RestController I'm successfully handling JSONParse exceptions coming from #RequestBody (for example, a String wrongly entered into an Integer field). This is the code:
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler({ HttpMessageNotReadableException.class })
public ValidationError handleException(HttpMessageNotReadableException ex) {
if (ex.getCause() instanceof InvalidFormatException) {
...
} else {
throw ex;
}
}
Now I want to move this to a #ControllerAdvice to be used by many controllers. Here it is:
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler({ HttpMessageNotReadableException.class })
public ValidationError handleException(HttpMessageNotReadableException ex) {
if (ex.getCause() instanceof InvalidFormatException) {
...
} else {
throw ex;
}
}
But Spring complains with the following:
Ambiguous #ExceptionHandler method mapped for [class org.springframework.http.converter.HttpMessageNotReadableException]: {public Object foo.bar.RestExceptionHandler.handleException(org.springframework.http.converter.HttpMessageNotReadableException), public final org.springframework.http.ResponseEntity org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler.handleException(java.lang.Exception,org.springframework.web.context.request.WebRequest) throws java.lang.Exception}
I can't override ResponseEntityExceptionHandler.handleException because it's final. What other options are there?
Using Spring Boot 2.4.3.
I can't override ResponseEntityExceptionHandler.handleException because it's final
You're supposed to override the protected ResponseEntity<Object> handleHttpMessageNotReadable(...) method instead for custom error handling.
You should not extend from ResponseEntityExceptionHandler class.
Check this answer https://stackoverflow.com/a/71119336/4757784
Related
I created a function on AWS Lambda with Spring Cloud and java 11.
I'm trying to catch the exceptions thrown with #ExceptionHandler but it's not working.
I'm using an API Gateway Http Api as a trigger
Is there any specific way to catch these exceptions with Spring Cloud?
Function
#Component
public class TestFunction implements Function<APIGatewayV2HTTPEvent, APIGatewayV2HTTPResponse> {
#Override
public APIGatewayV2HTTPResponse apply(APIGatewayV2HTTPEvent event) {
throw new TestException();
}
}
Handle
#ControllerAdvice
public class TestExceptionHandler {
#ExceptionHandler(value = {TestException.class})
public APIGatewayV2HTTPResponse handleTestException(TestException e) {
return APIGatewayV2HTTPResponse.builder()
.withStatusCode(422).build();
}
}
Handle - second attempt as suggested by #kladderradatsch
#ControllerAdvice(basePackageClasses = TestFunction.class)
public class TestExceptionHandler extends ResponseEntityExceptionHandler {
#ResponseBody
#ExceptionHandler(value = {TestException.class})
public ResponseEntity<?> handleTestException(TestException ex) {
return new ResponseEntity<>(new MyErrorBody(400, ex.getMessage()), HttpStatus.BAD_REQUEST);
}
}
Exception
public class TestException extends RuntimeException {
public TestException() {
super("Error Test.");
}
}
I tried to catch the exception in several different ways, including adding a #ResponseStatus(HttpStatus.BAD_REQUEST) to the exception, but that didn't work either.
I have two classes that implements ExceptionMapper interface.
IllegalArgumentExceptionMapper to handle IllegalArgumentException:
#Slf4j
#Provider
public class IllegalArgumentExceptionMapper implements ExceptionMapper<IllegalArgumentException> {
#Override
public Response toResponse(IllegalArgumentException exception) {
log.info("IllegalArgumentExceptionMapper!");
Error error =
Error.builder()
.statusCode(HttpStatus.SC_BAD_REQUEST)
.statusDescription(exception.getLocalizedMessage())
.errorMessage(exception.getMessage())
.build();
return Response.status(Response.Status.BAD_REQUEST)
.entity(error)
.type(MediaType.APPLICATION_JSON)
.build();
}
}
GenericExceptionMapper is an ExceptionMapper that I want to use as the default ExceptionMapper when an exception is not mapped to any of my other specific ExceptionMapper classes. Here it is:
#Slf4j
#Provider
public class GenericExceptionMapper implements ExceptionMapper<Throwable> {
#Override
public Response toResponse(Throwable ex) {
log.info("GenericExceptionMapper!");
Response.StatusType type = getStatusType(ex);
Error error = Error.builder()
.statusCode(type.getStatusCode())
.statusDescription(type.getReasonPhrase())
.errorMessage(ex.getLocalizedMessage())
.build();
return Response.status(error.getStatusCode())
.entity(error)
.type(MediaType.APPLICATION_JSON)
.build();
}
private Response.StatusType getStatusType(Throwable ex) {
if (ex instanceof WebApplicationException) {
return((WebApplicationException)ex).getResponse().getStatusInfo();
} else {
return Response.Status.INTERNAL_SERVER_ERROR;
}
}
}
However, when I try to throw an IllegalArgumentException, with:
throw new IllegalArgumentException("Just a normal IllegalArgumentException!");
I see that GenericExceptionMapper instead of IllegalArgumentExceptionMapper is being used.(I see "GenericExceptionMapper!" in the log).
Any idea what went wrong?
Some observations
If I delete GenericExceptionMapper, IllegalArgumentExceptionMapper is still not being called. So I think there is an issue for my IllegalArgumentExceptionMapper implementation.
If I modify IllegalArgumentExceptionMapper with public class IllegalArgumentExceptionMapper implements ExceptionMapper<RuntimeException> and throw new RuntimeException, then I see that IllegalArgumentExceptionMapper is being used.
I have some code like this
#Controller
class SomeController
#GetMapping("/someAsync")
#ResponseBody
public String someAsync() {
throw new SomeException();
}
#GetMapping("/somePage")
public String somePage() {
throw new SomeException();
}
in this case, i want to redirect to default error page for "/somePage"
and I need to response like { HttpStatus:500, message: "something happened" }
I made this ControllerAdvice
#ControllerAdvice
class SomeAdvice
#ExceptionHandler(Exception.class)
#ResponseBody
public Object handle(Exception e) {
// in this method, I want to know this Exception was called by a method that contains "#ResponseBody"
}
Could I know the method signature like this way?
I have a basic SpringBoot 2.0.4.RELEASE app. Using Spring Initializer, JPA, embedded Tomcat, Thymeleaf template engine, and package as an executable JAR file.
I have created this class to manage the exceptions
#ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
public RestResponseEntityExceptionHandler() {
super();
}
// API
// 400
...
}
But when I try to use it in 1 of my method :
#GetMapping(path = "/getUsers", consumes = "application/json", produces = "application/json")
#ExceptionHandler({RestResponseEntityExceptionHandler.class })
public ResponseEntity<List<User>> testErrors(HttpServletRequest request, #RequestHeader(value = "Authorization") String authHeader) {
...
}
I got this compilation error :
Type mismatch: cannot convert from Class to Class>
If you read the docs for #ExceptionHandler you will see that it is used to mark a method to handle exception(s). Therefore you have to specify which exception(s) it can handle (e.g. #ExceptionHandler(MyException.class) or #ExceptionHandler({MyException.class, MyOtherException.class})). When such an exception occurs in your controller this method gets invoked.
#ControllerAdvice makes the methods defined in the class available to all your controllers.
I don't know what you want to do, but since you extend ResponseEntityExceptionHandler in the #ControllerAdvice class, you could just delete the #ExceptionHandler annotation:
#GetMapping(...)
public ResponseEntity<List<User>> testErrors(...) {
...
}
If you want to handle specific exceptions on your own, you can do it like this:
#ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(MyException.class)
public ResponseEntity<?> handleMyException() {
...
}
}
#Controller
public class MyController {
#GetMapping(...)
public ResponseEntity<List<User>> testErrors(...) {
throw new MyException();
}
}
Now handleException() will be called when an error occurs in testErrors().
If you want to handle exceptions only in one controller you can do this:
#Controller
public class MyController {
#GetMapping(...)
public ResponseEntity<List<User>> testErrors(...) {
throw new MyException();
}
#ExceptionHandler(MyException.class)
public ResponseEntity<?> handleMyException() {
...
}
}
It's expected for this not to work correctly.
You're attempting to pass in the RestResponseEntityExceptionHandler as the arguments of the #ExceptionHandler annotation in your test method. This is wrong, as this annotation accepts the type of the exception that gets intercepted.
Also in general it seems that the placement of your #ExceptionHandler annotation seems to be wrong. This are placed within method that reside in the actual exception handling class.
I suggest you have a good read on the way Spring handles exceptions in that manner.
Have a look at this piece of documentation: exception handling in Spring.
I would like to manage Exception thrown by simple Controller or RestController in two ways:
1) html redirection
2) Json error
I tested the code below :
#ControllerAdvice(annotations = Controller.class)
public class ExceptionHandlerController
{
#ExceptionHandler(Exception.class)
public ModelAndView handleException(HttpServletRequest _req, Exception _ex)
{
K_LOGGER.info("test");
return new ModelAndView();
}
}
#ControllerAdvice(annotations = RestController.class)
public class ExceptionHandlerRestController
{
#ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(HttpServletRequest _req, Exception _ex)
{
return new ResponseEntity<>("test", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
#RestController
public class GreetingController
{
#RequestMapping("/greetingexception")
public Greeting greetingException(#RequestParam(value = "name", defaultValue = "World") String name)
throws Exception
{
throw new Exception();
}
}
It doesn't work properly, I always pass by ExceptionHandlerController but not by ExceptionHandlerRestController.
I think it's because #RestController inherit of #Controller.
Do you have a other solution?
Try to add #Order(Ordered.HIGHEST_PRECEDENCE) annotation to rest exception handler. It may helps you.
eg04lt3r answer is correct, just though that more details might be useful for someone.
In case when you have global #ControllerAdvice and want to handle some exception in a different way in one of your Controllers you need to set #Order(Ordered.HIGHEST_PRECEDENCE) on the #ControllerAdvice which should have higher priority.
For example:
#ControllerAdvice
public class GeneralExceptionHandler {
#ExceptionHandler(Exception.class)
protected ResponseEntity<Error> handleException(Exception ex) {
...
}
}
#ControllerAdvice(assignableTypes = MyController.class)
#Order(Ordered.HIGHEST_PRECEDENCE)
public class MyExceptionHandler {
#ExceptionHandler(Exception.class)
protected ResponseEntity<Error> handleException(Exception ex) {
...
}
}
#Order is needed because on startup one of the handlers will register with higher order automatically, anyway and your exception handling will become unpredictable. For example I recently saw a case when if you start an app using bootRun gradle task MyExceptionHandler was primary, but when started as jar GeneralExceptionHandler was primary.