I have some Service classes which contain multiple methods that throws error, an example of methods that throws an error:
public Optional<Item> getItemById(Long itemId) throws Exception {
return Optional.of(itemRepository.findById(itemId).
orElseThrow(() -> new Exception("Item with that id doesn't exist")));
}
Should I catch errors in the #ControllerAdvice annoted class?
How should I do it?
The controller marked with #ControllerAdvice will intercept any exception thrown in the stack called when a request arrives. If the question is if you should catch errors with ControllerAdvice, is up to you, but it allows you to customize the behaviour once a exception is thrown. To do it you should create a class like this:
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler({ Exception.class, MyCustomException.class }) //Which exceptions should this method intercept
public final ResponseEntity<ApiError> handleException(Exception ex){
return new ResponseEntity<>(body, HttpStatus.NOT_FOUND); //Or any HTTP error you want to return
}
}
Related
I have a class that catches exceptions using the Spring annotation #ExceptionHandler and this class support some custom exceptions as well. I would like that when I raise the exception CustomRuntimeException in this way throw new CustomRuntimeException(args...);, it will be caught and handled in the following method:
#ControllerAdvice
public class CutomExceptionManager {
// code
#ExceptionHandler({CustomRuntimeException.class})
#ResponseBody
private ResponseEntity<ErrorResource> handleCustomException(CustomException e, HttpServletRequest request, HttpServletResponse response) {
logger.error("unhandled exception: ", (Exception)e);
// other code
}
}
This doesn't work.
As mentioned in the comments by #Tom Elias, the method should be protected or public to be taken in account by Spring.
As an example, the following code is working.
#ExceptionHandler(ControllerException.class)
public ResponseEntity<ControllerException> handleControllerException(ControllerException controllerException) {
log.error(controllerException.getFullMessage(), controllerException);
return new ResponseEntity<>(controllerException, HttpStatus.valueOf(controllerException.getStatus()));
}
BTW, no need to add the #ResponseBody annotation to the method.
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 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.