Spring Boot default exceptions mapped to standard HTTP status codes - java

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

Related

Spring boot change override exception responses

I am trying to customize exception responses and use my own response structure, I am using below way :
#ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler
{
#ExceptionHandler(RuntimeException.class)
#ResponseBody
public ResponseEntity<String> handle(Exception ex, HttpServletRequest request)
{
...
}
}
But I have not accessed to the status code, I need status code that defined in exceptions via ResponseStatus:
#ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
public class ExtendSubscriptionReminderNotExistException extends RuntimeException
{
}
With java reflection mechanism, you can do it like so:
#ExceptionHandler(RuntimeException.class)
#ResponseBody
public ResponseEntity<String> handle(Exception ex, HttpServletRequest request) {
if (ex instanceOf ExtendSubscriptionReminderNotExistException) {
ResponseStatus status = ExtendSubscriptionReminderNotExistException.class.getAnnotation(ResponseStatus.class);
return ResponseEntity.status(status.value()).body(ex.getMessage());
}else{
//if it's not ExtendSubscriptionReminderNotExistException, do sth different
}
}
Here is an useful article on how to read annotation in java: Java Reflection - Annotations
If you want to override ResponseStatusExceptionResolver, then you should extends AbstractHandlerExceptionResolver and implement your own doResolveException like ResponseStatusExceptionResolver did, then create a configuration extending WebMvcConfigurationSupport and override configureHandlerExceptionResolvers, then spring will pick up your own exception resolver over the default one. The logic behind this is here.
we cannot change exception messages. However determine we can change the code and class, and throw a new one by overriding the same class with the same code and different message.
I may be wrong on this one, but to me it doesn't really make sense to use #ResponseStatus annotation and a custom ErrorHandler at the same time.
Annotations are supposed to make your code easier to understand and to avoid using such handlers.
If you really want to use the handler, I'd suggest to drop the annotation and store the corresponding status code in each exception (as a final static attribute for example).

Custom #ControllerAdvice in Spring for exception handling

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);
}

Why doesn't this generic exception handler in #ControllerAdvice get used?

I want a "catch-all" method for any exception of type that inherits from Exception. When I try to inject multiple things, it's never called.
This does not get called:
#ExceptionHandler(value = Exception.class)
public ResponseEntity<Object> handleException(Exception exception, HttpStatus status, WebRequest request) throws Exception
{
return null; //Temporary for testing
}
If I change to this, then it does get called:
#ExceptionHandler(value = Exception.class)
public ResponseEntity<Object> handleException(Exception exception) throws Exception
{
return null; //Temporary for testing
}
Why is that?
Shouldn't the annotation tell Spring Boot that it handles everything of type Exception?
The full class:
#ControllerAdvice
public class ExceptionHandler extends ResponseEntityExceptionHandler
{
#ExceptionHandler(value = {Exception.class, RuntimeException.class})
public ResponseEntity<Object> handleException(HttpStatus status, WebRequest request, Exception exception) throws Exception
{
return null; //Put breakpoint here
}
/*#ExceptionHandler(value = Exception.class)
public ResponseEntity<Object> handleException(Exception exception) throws Exception
{
return null;
}*/
}
Per javadoc HttpStatus is not part of allowed parameter types for ExceptionHandler methods:
Handler methods which are annotated with this annotation are allowed
to have very flexible signatures. They may have parameters of the
following types, in arbitrary order:
An exception argument: declared as a general Exception or as a more specific exception. This also serves as a mapping hint if the
annotation itself does not narrow the exception types through its
value().
Request and/or response objects (typically from the Servlet API). You may choose any specific request/response type, e.g. ServletRequest
/ HttpServletRequest.
Session object: typically HttpSession. An argument of this type will enforce the presence of a corresponding session. As a consequence,
such an argument will never be null. Note that session access may not
be thread-safe, in particular in a Servlet environment: Consider
switching the "synchronizeOnSession" flag to "true" if multiple
requests are allowed to access a session concurrently.
WebRequest or NativeWebRequest. Allows for generic request parameter access as well as request/session attribute access, without ties to
the native Servlet API.
Locale for the current request locale (determined by the most specific locale resolver available, i.e. the configured LocaleResolver
in a Servlet environment).
InputStream / Reader for access to the request's content. This will be the raw InputStream/Reader as exposed by the Servlet API.
OutputStream / Writer for generating the response's content. This will be the raw OutputStream/Writer as exposed by the Servlet API.
Model as an alternative to returning a model map from the handler method. Note that the provided model is not pre-populated with regular
model attributes and therefore always empty, as a convenience for
preparing the model for an exception-specific view.

#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.

Spring 3 controller exception handler implementation problems

I was hoping to implement a single "ExceptionController" to handle exceptions that are thrown in execution of my other controllers' methods. I hadn't specified any HandlerExceptionResolver in my application context so according to the API documentation the AnnotationMethodHandlerExceptionResolver should be started. I verified it as such in the source. So why doesn't the following work?
#Controller
public class ExceptionController {
#ExceptionHandler(NullPointerException.class)
public ModelAndView handleNullPointerException(NullPointerException ex) {
// Do some stuff
log.error(logging stuff)
return myModelAndView;
}
}
#Controller
public class AnotherController {
#RequestMapping(value="/nullpointerpath")
public String throwNullPointer() {
throw new NullPointerException();
}
}
I see in the debug logs that the three default exception handlers are asked for handling of the exception, but nothing is done and I see "DispatcherServlet - Could not complete request". Followed by the user being displayed the stacktrace and a 500 Internal error.
Make sure your Exception handler is returning a view that exists/maps to a handler.
You should write your exceptionhandler to the same class with which you want to handle, like the following.
#Controller
public class AnotherController {
#ExceptionHandler(NullPointerException.class)
public ModelAndView handleNullPointerException(NullPointerException ex) {
// Do some stuff.
log.error(logging stuff)
return myModelAndView;
}
#RequestMapping(value="/nullpointerpath")
public String throwNullPointer() {
throw new NullPointerException();
}
}
I don't think this is a good design. Controllers in Spring handle HTTP requests and map to URLs. I don't think "exception" fits into either bin. It feels like a misuse of Spring to me.
An exception is not an HTTP request. You don't map an exception to a URL. Therefore I'd conclude that controllers aren't intended to be treated as exception handlers.
Controllers are a part of the Spring API, but your design isn't using them as intended, so that's why it's not working. Re-think your design.

Categories