Giving this controller
#GetMapping("/test")
#ResponseBody
public String test() {
if (!false) {
throw new IllegalArgumentException();
}
return "blank";
}
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(Exception.class)
#ResponseBody
public String handleException(Exception e) {
return "Exception handler";
}
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
#ExceptionHandler(IllegalArgumentException.class)
#ResponseBody
public String handleIllegalException(IllegalArgumentException e) {
return "IllegalArgumentException handler";
}
Both ExceptionHandler match the IllegalArgumentException because it's a child of Exception class.
When I reach /test endpoint, the method handleIllegalException is called. If I throw a NullPointerException, the method handleException is called.
How does spring knows that it should execute the handleIllegalException method and not the handleException method ? How does it manage the priority when multiple ExceptionHandler match an Exception ?
(I thought the order or the ExceptionHandler declarations was important, but even if I declare handleIllegalException before handleException, the result is the same)
Spring MVC provides many different methods for Exception handling definitions.
In general, it will try to find the most "specific" exception handler registered to handle the exception. If there is no such a handler, it will try to check for the superclass of exception, maybe there is a handler for it, if it's not found as well, it will go one more level up and so on and so forth, from the most specific to the most general.
If you want to see it in the code of Spring, an entry point to learn this topic would be:
org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver
This class resolves the exceptions registered through #ExceptionHandler methods out of the beans registered in the application context. This class, in turn, uses the another class org.springframework.web.method.annotation.ExceptionHandlerMethodResolver
which is responsible for mapping all the methods marked with #ExceptionHandler annotation.
Related
I wrote my Spring Boot ProductController Class with productDetail method & handleMethodArgumentNotValid method . handleMethodArgumentNotValid method is annotated with #ExceptionHandler(MethodArgumentNotValid.class). It worked perfectly fine. After that I removed
handleMethodArgumentNotValid method from Controller class, as I would like to use #ControllerAdvice. But it is executing BaseException class of the project. It is not executing #ControllerAdvice method.
Here is my Controller class.
#PostMapping("/productDetail")
public void productDetail(#Valid #RequestBody ProductDetail productDetail) {
System.out.println("I am in Controller ProductDetail ....");
try {
iOrderService.updateProductDetail(productDetail);
} catch (Exception e) {
//Executes Base Exception class information here
...
}
}
Here is my ControllerAdvice .
#ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
#Override
#ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex,
HttpHeaders headers,
HttpStatus status,
WebRequest request
) {
same code that I had in handleMethodArgumentNotValid method of ProductController class here
ErrorResponse errorResponse = new ErrorResponse(
HttpStatus.UNPROCESSABLE_ENTITY.value(),
"Validation error. Check 'errors' field for details."
);
for (FieldError fieldError : ex.getBindingResult().getFieldErrors()) {
errorResponse.addValidationError(fieldError.getField(),
fieldError.getDefaultMessage());
}
return ResponseEntity.unprocessableEntity().body(errorResponse);
}
How can I handle MethodArgumentNotValidException so that it won't execute BaseException class?
Your global exception handler can only handle uncaught exceptions. So if you want it to handle anything thrown by iOrderService.updateProductDetail(productDetail);, you'll need to remove the try/catch.
I suspect that your test input to productDetail() is not actually causing a MethodArgumentNotValidException. Either that or your global exception handler is not included in your component scan. I'd recommend adding a "catchAll" method to your global exception handler for testing purposes. Just to see if it's catching any exceptions at all.
#ExceptionHandler(Exception.class)
protected ResponseEntity<ExceptionEnvelope> catchAll(Exception exception, WebRequest request) {
return buildResponse(HttpStatus.INTERNAL_SERVER_ERROR, exception, request);
}
Set a breakpoint in there and just see if you're able to hit it. I've had issues like this before and it ended up being that my assumptions were incorrect about which exceptions spring throws for different circumstances. Catching all exceptions like this will allow you to validate that the GlobalExceptionHandler is wired up properly, and will also tell you which exception is actually getting thrown.
I changed RestController annotation to #Controller annotation in Controller Class and annotated method with #ResponseBody and it worked.
Is it possible to catch all thrown exceptions of a particular type, say IllegalArgumentException, throughout the entire Spring Boot application and handle it one place? I want to introduce some additional logging for particular types of exception in application and I am looking for a way to avoid duplicating logic or method calls throughout the application?
Take a look at the annotation #ExceptionHandler(value=YourException.class) and #ControllerAdvice it allows you to handle custom exceptions. The Controller Advice class can handle the exception globally. We can define any Exception Handler methods in this class file.
#ControllerAdvice
public class ProductExceptionController {
#ExceptionHandler(value = ProductNotfoundException.class)
public ResponseEntity<Object> exception(ProductNotfoundException exception) {
return new ResponseEntity<>("Product not found", HttpStatus.NOT_FOUND);
}
}
Spring AOP can be used to address this cross cutting concern.
#AfterThrowing advice can be used for this purpose.
The name used in the throwing attribute must correspond to the name of
a parameter in the advice method. When a method execution exits by
throwing an exception, the exception is passed to the advice method as
the corresponding argument value. A throwing clause also restricts
matching to only those method executions that throw an exception of
the specified type
Example can be found here
Springboot provides us with the capability to handle exceptions globally using the #ControllerAdvice annotation . So, instead of handling exceptions and logging it in each controller, you could actually throw the exception from every controller and handle it in a single place like :
BusinessException extends RunTimeException {
public BusinessException(String message, Throwable cause) {
super(message, cause);
}
}
#ControllerAdvice
public class ExceptionController {
#ExceptionHandler(value
= { BusinessException.class,IllegalArgumentException.class})
protected ResponseEntity<Object> handleCustomException(
RuntimeException ex, WebRequest request) {
String bodyOfResponse = "This should be application specific";
return handleExceptionInternal(ex, bodyOfResponse,
new HttpHeaders(), HttpStatus.NOTFOUND, request);
}
}
In your case, you could create a custom exception class and throw your custom exception from where ever your custom logic is needed. So, your could then handle this custom exception globally to provide your logic. This is one way to handle exceptions globally without duplicating logic. You could also do this using spring aop using pointcut.
#Aspect
public class LoggingAspect {
#AfterThrowing (pointcut = "execution(* com.yourservice.yourpackage.*(..))", throwing = "ex")
public void logAfterThrowingAllMethods(Exception ex) throws Throwable
{
System.out.println("****LoggingAspect.logAfterThrowingAllMethods() " + ex);
}
}
Just add spring aop and aspectJ as dependencies for this approach.
Lets say I'm using #ExceptionHandler in my application.
If I define :
#ControllerAdvice
public class MyExceptionHandler {
#ExceptionHandler(value = Exception.class)
public boolean generic(Exception e) {
return e;
}
#ExceptionHandler(value =MyException.class)
public boolean myException(MyException e) {
return e;
}
}
Il my controller throws a MyException, will the generic exception handler be triggered too or only the best match with for the exception will be executed (here the MyException handler) ?
The exception handler will try to find the specific exception(MyException) handler firstly, if not it will try to find the generic exception(Exception).
so for your example, when controller throw MyException, the handler will invoke the MyException handler.
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
{#link #value()}. Request and/or response objects (Servlet API
or Portlet API).
reference: https://github.com/spring-projects/spring-framework/blob/5f4d1a4628513ab34098fa3f92ba03aa20fc4204/spring-web/src/main/java/org/springframework/web/bind/annotation/ExceptionHandler.java#L33
I have a exception class like follows
#ResponseStatus(value=HttpStatus.UNPROCESSABLE_ENTITY, reason="Unprocessable Entity") // 422
public class UnprocessableEntityException extends RuntimeException {
}
Now the status is not returned as 422 unless I write a specific handler in the Controller class like :
#ExceptionHandler(UnprocessableEntityException.class)
#ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
public String handleException(Exception ex) {
...
}
As I understand I should not need #ExceptionHandler in first place, not sure what am I missing.
Throwing a #ResponseStatus annotated exception from a controller method should be enough for the framework to write the HTTP status code - no #ExceptionHandler necessary.
The following will write a 422 Status on hitting the webapp root as expected:
#Controller
public class ExceptionController {
#RequestMapping("/")
public void action() {
throw new ActionException();
}
#ResponseStatus(value = HttpStatus.UNPROCESSABLE_ENTITY, reason = "nope")
public static class ActionException extends RuntimeException {}
}
This works courtesy of the ResponseStatusExceptionResolver which is created by Spring MVC by default - if it's not working for you, my guess is that this default exception resolver has been removed (by e.g. overriding WebMvcConfigurationSupport.configureHandlerExceptionResolvers or otherwise configuring your context's HandlerExceptionResolvers so that the ResponseStatusExceptionResolver is trumped.)
The exception thrown should not be handled by code or by other exception resolvers, for example it shouldn't be handled by #ExceptionHandler, because that will override the status code specified by the exception class's #ResponseStatus.
I know that Exception is the Parent of all exceptions but I thought when you set #ExceptionHandler with specific exception class this should handle that specific exception.
Maybe you can point what I have missed in following code so MethodArgumentNotValidException will go into processValidationError method not processError method.
#ControllerAdvice
public class ExceptionHandler {
#ExceptionHandler(Exception.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ResponseBody
public ValidationErrorDTO processError(Exception e) {
return processErrors(e);
}
}
#ControllerAdvice
public class OtherExceptionHandler {
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public ValidationErrorDTO processValidationError(MethodArgumentNotValidException ex) {
return processErrors(ex);
}
}
After your edit it's clear that you have more than one #ControllerAdvice class.
In short, the problem is that your ExceptionHandler class (and its #ExceptionHandler for Exception.class) gets registered first by Spring, and because Exception handler matches any exception, it will be matched before Spring ever gets to more specific handlers defined.
You can read detailed explanation in #Sotirios answer here.
I'd recommend you register only one ControllerAdvice and to make sure it extends ResponseEntityExceptionHandler, so the default handling of MethodArgumentNotValidException is not overwritten.
If you then wish to alter the logic of handling the MethodArumentNotValidException, you can override the handleMethodArgumentNotValid method.