Custom #ControllerAdvice in Spring for exception handling - java

I am trying to map exceptions from my rest controllers to responses which have a body, and to do it in a central place.
I have tried this:
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class RestErrorResponseExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleExceptionInternal(
Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
super.handleExceptionInternal(ex, body, headers, status, request);
return ResponseEntity.status(status).body(Error.from(status));
}
}
The problem is that the handler is never triggered.
If I define a custom method with #ExceptionHandler in my rest controllers, or extend something that has #ExceptionHandler, then all works well, but that introduces some bad design.
It is my understanding that Spring will first try to look in controller for exception handling methods, then it will check for registered handlers.
I am trying to verify the behaviour via WebMvcTest, and responses I'm getting are not the Error objects that I'm expecting.
Is there something I'm missing?

The ControllerAdvice is a configuration that have to be registered by Spring. You have to move your class in the config package or you can register it by annotation.
In my case, I work with a controllerAdvice like this one :
#ControllerAdvice
public class GlobalControllerExceptionHandler {
#ExceptionHandler(MyException.class)
public ResponseEntity<String> reponseMyException(Exception e) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("my message");
}
}

Spring Framework provides following ways to help us achieving robust exception handling.
Controller Based – We can define exception handler methods in our controller classes. All we need is to annotate these methods with #ExceptionHandler annotation. This annotation takes Exception class as argument. So if we have defined one of these for Exception class, then all the exceptions thrown by our request handler method will have handled.
These exception handler methods are just like other request handler methods and we can build error response and respond with different error page. We can also send JSON error response, that we will look later on in our example.
If there are multiple exception handler methods defined, then handler method that is closest to the Exception class is used. For example, if we have two handler methods defined for IOException and Exception and our request handler method throws IOException, then handler method for IOException will get executed.
Global Exception Handler – Exception Handling is a cross-cutting concern, it should be done for all the pointcuts in our application. We have already looked into Spring AOP and that’s why Spring provides #ControllerAdvice annotation that we can use with any class to define our global exception handler.
The handler methods in Global Controller Advice is same as Controller based exception handler methods and used when controller class is not able to handle the exception.
HandlerExceptionResolver – For generic exceptions, most of the times we serve static pages. Spring Framework provides HandlerExceptionResolver interface that we can implement to create global exception handler. The reason behind this additional way to define global exception handler is that Spring framework also provides default implementation classes that we can define in our spring bean configuration file to get spring framework exception handling benefits.
SimpleMappingExceptionResolver is the default implementation class, it allows us to configure exceptionMappings where we can specify which resource to use for a particular exception. We can also override it to create our own global handler with our application specific changes, such as logging of exception messages.

Make sure of 2 things and your code will work.
Your #ControllerAdvice class is available in component-scan path.
Make sure the methods in your #ControllerAdvice have structure somewhat like this-
#ExceptionHandler(value = { RequestProcessingException.class })
public #ResponseBody ResponseEntity<ErrorMessageBO> hotelConfigServiceExceptionHandler(HttpServletRequest request, RequestProcessingException e) {
logger.error("Exception with tracking Id: {}, dev message: {} and Message:", RequestContextKeeper.getContext().getRequestId(), e.getDeveloperMessage(),e);
return new ResponseEntity<ErrorMessageBO>(new ErrorMessageBO(e.getErrorCode(), e.getMessage(),RequestContextKeeper.getContext().getRequestId(),e.getDeveloperMessage()), HttpStatus.OK);
}

Related

Unexpected behaviour when Customizing ExceptionResolver in spring

Through the documentation of spring framework, I got to know about customizing exception handling mechanism of spring application. As per the documentation, It is stated that spring provides some default exception Resolvers.
SimpleMappingExceptionResolver
A mapping between exception class names and error view names. Useful for rendering error pages in a browser application.
DefaultHandlerExceptionResolver
Resolves exceptions raised by Spring MVC and maps them to HTTP status codes. See also alternative ResponseEntityExceptionHandler and REST API exceptions.
ResponseStatusExceptionResolver
Resolves exceptions with the #ResponseStatus annotation and maps them to HTTP status codes based on the value in the annotation.
ExceptionHandlerExceptionResolver
Resolves exceptions by invoking an #ExceptionHandler method in a #Controller or a #ControllerAdvice class. See #ExceptionHandler methods.
But in my requirement, I do not want the support of all these resolvers I just want ExceptionHandlerExceptionResolver to handle the exception in my Spring Application. So for that, I have added the following Configuration in my DispatcherServlet.
DispatcherServlet related configuration.
public void onStartup(ServletContext servletCxt) throws { ServletException {
......
dServlet.setDetectAllHandlerExceptionResolvers(false);
dServlet.setThrowExceptionIfNoHandlerFound(false);
.....
}
And in my Bean configuration java class, I have created following bean.
#Bean(name=DispatcherServlet.HANDLER_EXCEPTION_RESOLVER_BEAN_NAME)
HandlerExceptionResolver customExceptionResolver ( ) {
return new ExceptionHandlerExceptionResolver();
}
In the documentation, it is stated that
/**
* Set whether to detect all HandlerExceptionResolver beans in this servlet's context. Otherwise,
* just a single bean with name "handlerExceptionResolver" will be expected.
* Default is "true". Turn this off if you want this servlet to use a single
* HandlerExceptionResolver, despite multiple HandlerExceptionResolver beans being defined in the context.
*/
But even after all such configuration, I see HTML error pages and not JSON error response.
Sample ExceptionHandler method.
#ExceptionHandler(value={NullPointerException.class})
public ResponseEntity<Object> nullPointerExceptionHandler(NullPointerException e){
logger.info("local exception resolver running");
Map<String,Object> map=new HashMap<String,Object>(6);
map.put("isSuccess",false);
map.put("data",null);
map.put("status",HttpStatus.INTERNAL_SERVER_ERROR);
map.put("message", e.getMessage());
map.put("timestamp",new Date());
map.put("fielderror",null);
return new ResponseEntity<Object>(map,HttpStatus.BAD_GATEWAY);
}
What I am doing wrong? I only need the support of ExceptionHandlerExceptionResolver throughout my spring application to maintain consitency.

Spring Boot default exceptions mapped to standard HTTP status codes

I know it is possible to define custom exception handlers in Spring Boot and have e.g. the IllegalArgumentException exception mapped to the HTTP 400 Bad Request. I am wondering are there any existing exceptions defined in Spring Web/Boot mapped to the standard HTTP status codes? So that I just can throw them and they will be automatically mapped to the standard HTTP status codes.
Effectively, ResponseEntityExceptionHandler will, by default, transform Spring internally thrown exceptions to an HTTP status code. However, converting the exception to an HTTP status code does not provide any significant logs about the exception. Good security practices dictate that externally dispatched error message shall be the least informative possible about the internals of a system. Conversely logs shall be as informative as could be.
Moreover, the ResponseEntityExceptionHandler only handle Spring generated exceptions. All business related exceptions must be handled separately. For instance, a "Record not found" exception thrown from a findSomething(xxx) method is not handled by this class.
Following are examples on how to address these shortcomings:
Spring threw internal errors
You must override the handler of the exception(s) of interest and provide both an internal log message and an external error message to be returned to the caller.
The #ControllerAdvice is an annotation that wraps #Component classes with classes declaring #ExceptionHandler annotated methods. Simply put, these handlers will wrap all #Component methods.
#Slf4j
#ControllerAdvice
public class InternalExceptionHandler extends ResponseEntityExceptionHandler {
#Override
public ResponseEntity<Object> handleMissingServletRequestParameter(
MissingServletRequestParameterException e,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
LogError error = new LogError("MissingServletRequestParameterException",
HttpStatus.BAD_REQUEST,
String.format("Missing '%s' parameter", e.getParameterName()));
log.debug(error.toJson());
HttpErrorResponse response = new HttpErrorResponse(error.getStatus(), e.getMessage());
return new ResponseEntity<>(response.toJson(),
HeaderFactory.getErrorHeaders(),
response.getStatus());
}
....
}
Business layer thrown errors
You must first create a specific RuntimeException class for each of these exceptions and annotated it woth #ResponseStatus.
#ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Record not found") //
public class RecordNotFoundException extends RuntimeException {
private static final long serialVersionUID = 8857378116992711720L;
public RecordNotFoundException() {
super();
}
public RecordNotFoundException(String message) {
super(message);
}
}
Then, you create an #ControllerAdvice annotated class that will hold all these exceptions handler method. There are no class to derive from as the internal redirection to these #ExceptionHandler annotated methods are managed by Spring.
#Slf4j
#ControllerAdvice
public class ClientExceptionHandler {
#ExceptionHandler(value = RecordNotFoundException.class)
public ResponseEntity<String> handleRecordNotFoundException(
RecordNotFoundException e,
WebRequest request) {
LogError logging = new LogError("RecordNotFoundException",
HttpStatus.NOT_FOUND,
request.getDescription(true));
log.info(logging.toJson());
HttpErrorResponse response = new HttpErrorResponse(logging.getStatus(), e.getMessage());
return new ResponseEntity<>(response.toJson(),
HeaderFactory.getErrorHeaders(),
response.getStatus());
}
....
}
Finally, the helper classes LogError and HttpErrorResponse are simple formatters for their respective destination.
Hope this helps.
Jake
There is a handful e.g. HttpRequestMethodNotSupportedException which maps to 405.
Take a look at ResponseEntityExceptionHandler.handleException() method which defines basic rules for handling common exceptions in Spring MVC. You will find
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

#ExceptionHandler for Error gets called only if there's no mapping for Exception

Using spring-web-4.2.6, I have the following Controller and ExceptionHandler:
#ControllerAdvice
public class ExceptionsHandler {
#ExceptionHandler(Exception.class)
public ResponseEntity<ErrorDTO> HandleDefaultException(Exception ex) {
...
}
#ExceptionHandler(InternalError.class)
public ResponseEntity<ErrorDTO> HandleInternalError(InternalError ex) {
...
}
}
#RestController
#RequestMapping("/myController")
public class MyController {
#RequestMapping(value = "/myAction", method = RequestMethod.POST)
public boolean myAction() {
throw new InternalError("");
}
}
For some reason, the ExceptionsHandler's HandleDefaultException (for Exception.class) method is invoked, with an exception of type NestedServletException, instead of the HandleInternalError call.
When removing the default call, the IntenalError call is called with the proper InternalError exception.
I do not want to remove the default call as it is important to me to have a default handler to allow for a better experience for my users.
What am I missing here?
EDIT:
Apparently I'm using spring-web-4.3.3, without asking for it. I don't understand why exactly, here's my Gradle dependencies tree: http://pastebin.com/h6KXSyp2
Spring MVC should only exhibit the behavior you describe with version 4.3 and above. See this JIRA issue. Previously, Spring MVC would not expose any Throwable values to #ExceptionHandler methods. See
ExceptionHandler doesn't work with Throwable
Since 4.3, Spring MVC will catch any Throwable thrown from your handler methods and wrap it in a NestedServletException, which it will then expose to the normal ExceptionHandlerExceptionResolver process.
Here's a short description of how it works:
Checks if the handler method's #Controller class contains any #ExceptionHandler methods.
If it does, tries to resolve one that can handle the Exception type (including NestedServletException). If it can, it uses that (there's some sorting if multiple matches are found). If it can't, and the Exception has a cause, it unwraps and tries again to find a handler for that. That cause might now be a Throwable (or any of its subtypes).
If it doesn't. It gets all the #ControllerAdvice classes and tries to find a handler for the Exception type (including NestedServletException) in those. If it can, it uses that. If it can't, and the Exception has a cause, it unwraps it and tries again with that Throwable type.
In your example, your MyController throws an InternalError. Since this is not a subclass of Exception, Spring MVC wraps it in an NestedServletException.
MyController doesn't have any #ExceptionHandler methods, so Spring MVC skips it. You have a #ControllerAdvice annotated class, ExceptionsHandler, so Spring MVC checks that. The #ExceptionHandler annotated HandleDefaultException method can handle Exception, so Spring MVC chooses it to handle the NestedServletException.
If you remove that HandleDefaultException, Spring MVC won't find something that can handle Exception. It will then attempt to unwrap the NestedServletException and check for its cause. It'll then find the HandleInternalError which can handle that InternalError.
This is not an easy issue to deal with. Here are some options:
Create an #ExceptionHandler that handles NestedServletException and do the check for InternalError yourself.
#ExceptionHandler(NestedServletException.class)
public ResponseEntity<String> HandleNested(NestedServletException ex) {
Throwable cause = ex.getCause();
if (cause instanceof InternalError) {
// deal with it
} else if (cause instanceof OtherError) {
// deal in some other way
}
}
This is fine unless there's a bunch of different Error or Throwable types you want to handle. (Note that you can rethrow these if you can't or don't know how to handle them. Spring MVC will default to some other behavior, likely returning a 500 error code.)
Alternatively, you can take advantage of the fact that Spring MVC first checks the #Controller (or #RestController) class for #ExceptionHandler methods first. Just move the #ExceptionHandler method for InternalError into the controller.
#RestController
#RequestMapping("/myController")
public class MyController {
#RequestMapping(value = "/myAction", method = RequestMethod.POST)
public boolean myAction() {
throw new InternalError("");
}
#ExceptionHandler(value = InternalError.class)
public ResponseEntity<String> HandleInternalError(InternalError ex) {
...
}
}
Now Spring will first attempt to find a handler for NestedServletException in MyController. It won't find any so it will unwrap NestedServletException and get an InternalError. It will try to find a handler for InternalError and find HandleInternalError.
This has the disadvantage that if multiple controllers' handler methods throw InternalError, you have to add an #ExceptionHandler to each. This might also be an advantage. Your handling logic will be closer to the thing that throws the error.

What are the advantages of #ControllerAdvice over #ExceptionHandler or HandlerExceptionResolver for handling exceptions?

Spring 3.2 introduced #ControllerAdvice annotation for handling exceptions in a Spring MVC application. But prior to this version Spring had #ExceptionHandler or HandlerExceptionResolver to handling exceptions in a Spring MVC application. Then why Spring 3.2 introduced #ControllerAdvice annotation for handling exceptions? I strongly believe that Spring 3.2 introduced #ControllerAdvice annotation to address the limitations of #ExceptionHandler or HandlerExceptionResolver or make the exceptions handling more stronger.
Can any one explain the advantages of #ControllerAdvice over #ExceptionHandler or HandlerExceptionResolver for handling exceptions?
#ExceptionHandler
#ExceptionHandler works at the Controller level and it is only active for that particular Controller, not globally for the entire application.
HandlerExceptionResolver
This will resolve any exception thrown by the application. It is used to resolve standard Spring exceptions to their corresponding HTTP Status Codes. It does not have control over the body of the response, means it does not set anything to the body of the Response.It does map the status code on the response but the body is null.
#ControllerAdvice
#ControllerAdvice used for global error handling in the Spring MVC application.It also has full control over the body of the response and the status code.
A #ExceptionHandler is local to a controller : only exceptions from this controller is routed to his #ExceptionHandler
But a #ControllerAdvice is global : you can have a centralized way to handle exceptions, binding, etc. it applies to all the defined controller.
Here is the difference:
If I need to configure the exception handling code then I need to use #ExceptionHandler annotation in my project which can be used in two diff.ways:
1)To use the annotation and handle the exception in the same controller locally in each controller class.
for eg:
#RestController
public class WSExposingController{
#GetMapping("/getAllDetails/{/id}")
public UserDetails myMethod(#PathVariable int id){
UserDetails user = UserDetailsService.getUser(id);
return user;
}
//To handle the exceptions which are resulting from this controller, I can declare an exceptionHandler specifically in the same class
#ExceptionHandler(Exception.class)
public ResponseEntity handlerSameControllerExceptions(){
return new ResponseEntity(null,HttpStatus.INTERNAL_SERVER_ERROR);
}
}
2)If I create a new class which extends
ResponseEntityExceptionHandler(SpringBoot class) and if I ANNOTATE IT WITH
#ControllerAdvice then that class becomes
globalexceptionhandler meaning to say that any exception which is resulting in any controller class can be handled here. And it can be present in any package
in the same project.
#RestController
#ControllerAdvice
public class GlobalJavaExceptionHandler extends ResponseEntityExceptionHandler{
#ExceptionHandler(Exception.class)
public ResponseEntity handleDiffControllerExceptions(){
return new ResponseEntity(null,HttpStatus.INTERNAL_SERVER_ERROR);
}
If both are present in the code then the local with take precedence over the global one.
Ideally the second option is a better one as we need not add the code in each and every controller class and this class with #ControllerAdvice can be a one stop solution for all the exceptions arising due to the code beginning from the controller to the dao throughout the length of the code.
#ExceptionHandler can be used at the local level or at the global level.
Local level would mean using this annotation within the controller itself to handle the exceptions within that controller only. All error thrown by that controller would be caught by that #ExceptionHandler.
But this would mean that if there is a similar exception in a different controller you would have to rewrite the corresponding code again in that controller again locally.
In order to prevent repeating this style of exception handling per controller we can write the #ExceptionHanlder at the global level with the help of another annotation called #ControllerAdvice.
#ControllerAdvice is not specific to the exception handling , its also used for handling property, validation or formatter bindings at the global level.
#ControllerAdvice in the context of exception handling is just another way of doing exception handling at a global level using #Exceptionhandler annotation.
Now coming to the HandlerExceptionResolver - this is an interface at a more lower level. Spring provides 2 implementations of this:
ResponseStatusExceptionResolver :This supports the support the #ResponseStatus annotation
ExceptionHandlerExceptionResolver : This supports the #ExceptionHandler annotation
Example : So when you want to handle exceptions and choose an exception handling strategy you will need to think of choosing between using a local or global exception handling via the annotations. How you need to provide the HTTP status codes, how to wrap it in the #Response entity etc, how you want to redirect to handler pages , carry the data via flash attributes or get params etc etc.
Or maybe skip the annotations and use the SimpleMappingExceptionResolver and start mapping the specific exceptions to the error handler page urls
Here we are not be considering the lower level underlying HandlerExceptionResolver at this stage since we are dealing with its implementation at a higher level and building the strategy based on these options.
With the above context to answer your query - #ControllerAdvice was not introduced for exception handling, it's a mechanism you can leverage to handle exceptions globally using the #ExceptionHandler. HandlerExceptionResolver is an interface whose implementation helps support the #ResponseStatus and the #Exceptionhandler annotations. Unless you want to handle the exceptions related to the MVC system because the Spring framework does not provide any proper exception handling. So if you need to hand;e issues related to incorrect #Requestmapping etc which would not be caught by a controller as it would not even reach it in the 1st place then an implementation of the HandlerExceptionResolver would be useful

Exception Logging with Spring

I have heard that it is possible to log (or do something else) Exceptions with Spring in my web-App, so I don't have to manually insert in every "catch(){}" block the Log-function.
Does anyone have experience with Spring-overall-logging? I just want to get informed when an error appears
ExceptionHandler is the central point for handling unexpected Exceptions that are thrown during the Faces lifecycle. The ExceptionHandler must not be notified of any Exceptions that occur during application startup or shutdown.
See the specification prose document for the requirements for the default implementation. Exceptions may be passed to the ExceptionHandler in one of two ways:
1.)By ensuring that Exceptions are not caught, or are caught and re-thrown.
This approach allows the ExceptionHandler facility specified in section JSF.6.2 to operate on the Exception.
2.)By using the system event facility to publish an ExceptionQueuedEvent that wraps the Exception.
This approach requires manually publishing the ExceptionQueuedEvent, but allows more information about the Exception to be stored in the event. The following code is an example of how to do this.
Global Exception Handler – Exception Handling is a cross-cutting concern, it should be done for all the pointcuts in our application. We have already looked into Spring AOP and that’s why Spring provides #ControllerAdvice annotation that we can use with any class to define our global exception handler.
The handler methods in Global Controller Advice is same as Controller based exception handler methods and used when controller class is not able to handle the exception.
Sample Code
#ExceptionHandler(Exception.class)
public ModelAndView getExceptionPage(Exception e, HttpServletRequest request) {
request.setAttribute("errorMessageObject", e.toString());
return model;
}
** Here we can catch the base exception class Exception.class or any other exception class. Also we can throw and catch our own custom defines exception class.

Categories