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

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.

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

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

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

Spring Boot how to ignore HttpStatus Exceptions

I'm building an Application using Spring Boot. This application is distributed, which means I have multiple API's that call each others.
One of my underlying services interacts with a database and responds with the requested data. If a request to an unexisting ID is made, I response with a 404 HttpStatus:
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
(Same with 400 error on certain operations, or 204 for deleting an entry etc).
The problem is that I have some other Spring Boot applications that call these API's, throw an org.springframework.web.client.HttpClientErrorException: 404 Not Found Exception when they request, in this example, an unexisting entry. But the 404 status code is intended and should not return this exception (causing my Hystrix circuit breaker to call its fallback function).
How can I solve this problem?
The call to the service is implemented like this in my code: ResponseEntity<Object> data = restTemplate.getForEntity(url, Object.class);
My RestTemplate is set up like this:
private RestTemplate restTemplate = new RestTemplate();
Spring's RestTemplate uses a ResponseErrorHandler to handle errors in responses. This interface provides both a way to determine if the response has an error (ResponseErrorHandler#hasError(ClientHttpResponse)) and how to handle it (ResponseErrorHandler#handleError(ClientHttpResponse)).
You can set the RestTemplate's ResponseErrorHandler with RestTemplate#setErrorHandler(ResponseErrorHandler) whose javadoc states
By default, RestTemplate uses a DefaultResponseErrorHandler.
This default implementation
[...] checks for the status code on the
ClientHttpResponse: any code with series
HttpStatus.Series.CLIENT_ERROR or HttpStatus.Series.SERVER_ERROR is
considered to be an error. This behavior can be changed by overriding
the hasError(HttpStatus) method.
In case of an error, it throws the exception you are seeing.
If you want to change this behavior, you can provide your own ResponseErrorHandler implementation (maybe by overriding DefaultResponseErrorHandler) which doesn't consider 4xx as an error or that doesn't throw an exception.
For example
restTemplate.setErrorHandler(new ResponseErrorHandler() {
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return false; // or whatever you consider an error
}
#Override
public void handleError(ClientHttpResponse response) throws IOException {
// do nothing, or something
}
});
You can then check the status code of the ResponseEntity returned by getForEntity and handle it yourself.

Ensure that web service handles exceptions and always returns a valid response

I am developing a web service, no matter what, the response should always be valid, and I mean valid as in a valid format.
That means that if the web service is expected to return an XML matching a particular XSD schema, it should ALWAYS return a valid XML document, no matter what.
The only approach that I have so far is to do something like this (at controller level)
String xmlResponse = this.loadDefaultXML();
try {
xmlResponse = this.myCoolService.myCoolMethod();
} catch (Throwable t) {
xmlResponse = this.loadDefaultXML(String errorMessage)
} finally {
return xmlResponse
}
Where of course lpoadDefaultXML() will load an xml document like:
<?xml>
<result>Ouch, there was a problem</result>
And loadDefaultXML(String errorMessage) will do
<?xml>
<result>WHATEVER errorMessage contains</result>
Of course the service level takes cares of the normal exceptions, still, I feel that catching Throwable and using the try-catch-finally is the only way to ensure that no matter what, I will be in control so I can return always an XML.
Any better ideas or suggestions?
UPDATE:
I am using Spring MVC 3.2 and JAXB for the marshalling/unmarshalling of xml.
This does use SOAP, also I am not using wsdl for this.
In Spring MVC, when an exception is thrown during the handling of the request, the DispatcherServlet will consult the configured org.springframework.web.servlet.HandlerExceptionResolvers to handle the thrown exception. The resolver can then translate the exception to a view to show the user.
To use it, in short, you can either:
Implement the HandlerExceptionResolver interface, which is only a matter of implementing the resolveException(Exception, Handler) method and returning a ModelAndView.
Or, what I prefer:
You use the #ExceptionHandler method annotation within a controller to specify which method is invoked when an exception of a specific type is thrown during the execution of controller methods. For example:
#Controller
public class PersonController {
#RequestMapping("person/{id}")
#ResponseBody
public Person getById(#PathVariable String id) {
if ("007".equals(id)) {
throw new RuntimeException("007 is a secret agent.");
}
return personService.getById(id);
}
#ExceptionHandler(RuntimeException.class) // this can be an array
#ResponseBody
public String handleRuntimeException(RuntimeException ex,
HttpServletRequest request) {
return "Oops! Something bad happened: "+ex.getMessage();
}
}
Find more info at Web MVC framework - Handling Exceptions.

Categories