Generic exception handler prevents specific exception being handled - spring

I have a spring boot application and there I handle exceptions. I have two exception handler methods for now:
#ControllerAdvice
#RestController
public class MyExceptionHandler {
#ExceptionHandler(Exception.class)
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public final BaseResponse<GenericException> handleGenericExceptions(Exception ex, HttpServletRequest request) {
....
return response;
}
#ExceptionHandler(MyCustomException.class)
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public final BaseResponse<MyCustomException> handleMyCustomException(MyCustomException ex, HttpServletRequest request) {
....
return response;
}
}
At some point, I throw a MyCustomException exception, but when I debug I see that it is handled by handleGenericExceptions method. If I delete handleGenericExceptions method, then it is handled by handleMyCustomException message.
I want MyCustomException to be handled by handleMyCustomException method, and all other exceptions by handleGenericExceptions method. MyCustomException class extends Throwable. What is wrong here?
Thanks.

I found the solution, but I do not now why this is the case.
MyCustomException class was extending Throwable, I changed it to extend RunTimeException instead. Now it is working as expected.

I don't have 50 repuatations,so I answered here, hope it make sense.
Throwable class have two important subclass ,Error and Exception . your MyCustomException extend Throwable. In spring , if your code throws which extends Throwable , Spring defaultly will decorate it as an IllegalStateException! I see this in spring4.3 source code , if you want to prove this , you add #ExceptionHandler({IllegalStateException}) and make your MyCustomException unmodified , this method will catch it!
Above evidence is in org.springframework.web.method.support.InvocableHandlerMethod#doInvoke.
By the way , we usually throw Exception by extends RuntimeException , why you want to throw an Throwable .

Related

How to handle exception in controller method with controlleradvice being in place

I do have a Spring boot controller class and a corresponding ControllerAdvice class which has ExceptionHandlers to handle different exception.
My controller method calls a simple validation helper class to validate input fields which throws an exception if validation fails. Now if I don't put a try catch block in my controller it keeps complaining me that you have a method which has untangled exception even through the logic for handling validation exception is defined in controlleradvice class. Please suggest how do I solve it.
From the method of ValidationHelper class if you throw any Checked Exception then you need to use a try-catch block to call that method.
If you don't want then it's better to use any Custom Exception class which will extend the RuntimeException class and you throw that exception. Then you don't need to explicitly mention the throws as well as you don't need to have a try-catch block at the controller.
#RestController
#RequiredArgsConstructor
public class SampleController {
private final ValidationHelper validationHelper;
#ResponseStatus(HttpStatus.OK)
#GetMapping("/sample")
public String getRequest(#RequestParam String name) {
validationHelper.validate(name);
return "";
}
}
#Service
public class ValidationHelper {
public Boolean validate(String name) {
throw new CustomException("Validation Failed");
}
}
public class CustomException extends RuntimeException {
public CustomException(String message) {
super(message);
}
}

Http status code from a global #ExceptionHandler with the #ControllerAdvice annotation

I'm implementing a global exception handler inside a Spring Boot App, with the #ControllerAdvice annotation, and I'd like to know, how could I get the http status code for showing a different message when it's 404 and to persist a log with the error, in other cases.
This is a simplified version of the code:
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(RuntimeException.class)
public ModelAndView handleException(Exception ex, HttpServletRequest request, HttpServletResponse response) {
...
ModelAndView model = new ModelAndView();
model.addObject("message", ex.getMessage());
model.addObject("trace", trace);
model.addObject("path", path);
//model.addObject("status", response.getStatus());
model.setViewName("error");
return model;
}
I've tried this approach, without success:
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
Integer statusCode = Integer.valueOf(status.toString());
To get the request attribute, this other name; javax.servlet.error.status_code doesn't work either.
You have to set your own status code corresponding every exception that you are handling. If any exception missed, default will be 5.x.x server error.
I remember doing this by extracting the expected exception to a separate class that extends Exception.
By doing this, you can add #ResponseStatus to set your required status code.
This custom exception can be thrown in your controller needed.
#ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Person Not Found")
public class PersonNotFoundException extends Exception {
public PersonNotFoundException (int id){
super("PersonNotFoundException with id="+id);
}
}
Instead of specifying the generic RunTime exception, handle the PersonNotFoundException in your #ExceptionHandler and add the exception object to your ModelAndView.

CustomExceptionHandler not able to catch exceptions in spring boot

In my spring boot application, I have created a custom exception handler using #ControllerAdvice, and a custom exception ServerException, when I throw the custom exception, it does not get caught by my customExcpetionHandler, though I am able to check whether actually the excpetion is thrown and it is getting thrown as shown by logs.
Below is the code for my ServerException:
public class ServerException extends Exception {
/**
*
*/
private static final long serialVersionUID = <uid>;
public ServerException(String message) {
super(message);
}
}
Below is my GlobalCustomExceptionHandler class:
#ControllerAdvice
#EnableWebMvc
public class GlobalCustomExceptionHandler extends ResponseEntityExceptionHandler{
#ExceptionHandler(ServerException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public ModelMap handleServerException(ServerException ex) {
ModelMap modelMap = new ModelMap();
modelMap.addAttribute("status", "ERROR_400_Bad_Request");
modelMap.addAttribute("error_message", ex.getMessage());
return modelMap;
}
}
I am throwing the exception in one of the restcontroller as follows:
throw new ServerException("invalid server configs");
But I can only see the exception getting printed in log file, and not getting it as response mentioned in handleServerException() method of GlobalCustomExceptionHandler class.
What could be the reason ?
I have just reproduced Your copy-pasted piece of code with simple REST endpoint, and it works as expected:
#RestController
public class SystemController {
#GetMapping(value = "/system")
public ResponseEntity<Object> getSystem() throws ServerException {
if (true)
throw new ServerException("Checking this out");
return new ResponseEntity<>(HttpStatus.OK);
}
}
Calling http://localhost:8080/system
Results with:
{"status":"ERROR_400_Bad_Request","error_message":"Checking this out"}
I need bigger picture to help You. Paste controller that is throwing that as well as main application config class.

#RestControllerAdvice and #ControllerAdvice together

I have an Spring MVC application which has #Controller s and #RestController s.
I was thinking that: When I have some Exception at my #Controller, It gonna be handled by my #ControllerAdvice and when I have some Exception at my #RestController, It gonna be handled by my #RestControllerAdvice... But now I think It's not how things should work, because my #ControllerAdvice are catching everything, even any exception that is thrown by #RestController...I do not know if this should happen. Here my code:
#ControllerAdvice
public class ExceptionHandlerController {
private final String DEFAULT_ERROR_VIEW = "error/default";
#ExceptionHandler(Exception.class)
public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e)
{
ModelAndView mav = new ModelAndView();
mav.addObject("exception", e);
mav.addObject("danger", e.getMessage());
mav.addObject("url", req.getRequestURL());
mav.setViewName(DEFAULT_ERROR_VIEW);
return mav;
}
}
#RestControllerAdvice
public class ExceptionHandlerRestController {
#ExceptionHandler(Exception.class)
public ResponseEntity<String> defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
return new ResponseEntity<>(" test "+e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Yeah #RestControllerAdvice doesn't work that way. It's just a #ControllerAdvice with #ResponseBody automatically assumed. See #RestControllerAdvice vs #ControllerAdvice.
If you wanted one #ControllerAdvice to work with one controller and one to work with the other then if you put your controllers in separate packages you should be able to do this by doing:
#ControllerAdvice("my.controller1.package")
However, the whole point of #ControllerAdvice is to share common functionality of your separate #Controllers between each other so if you want them to do separate things you might be better off just putting the methods inside the #Controllers themselves.
If you want #RestControllerAdvice to handle only exceptions thrown from #RestController, then you can qualify it with the annotations attribute:
#RestControllerAdvice(annotations = RestController.class)
You may need #Order tag if you happen to have several other #ControllerAdvice.

Send redirect url with #ResponseStatus(value = HttpStatus.MOVED_PERMANENTLY)

I am using an exception class for #ResponseStatus , but I am not able to figure out how do I send redirect URL in case of 301 Permanently Moved error ?
Exception class:
#ResponseStatus(value = HttpStatus.MOVED_PERMANENTLY)
public class ResourceMovedPermanentlyException extends RuntimeException{
}
You can use a Spring's RedirectView
RedirectView rv = new RedirectView(url);
rv.setStatusCode(HttpStatus.MOVED_PERMANENTLY);
rv.setUrl(url);
ModelAndView mv = new ModelAndView(rv);
return mv;
I don't think you can add the url with the simplified exception handling you are using.
Check out vzamanillo's solution (use it in a class that is annotated with #ControllerAdvice and a method annotated with #ExceptionHandler).
For the whole story of Spring MVC exception handling check out this blog post
The question is not very clear. I suppose you want to redirect to a page in case of ResourceMovedPermanentlyException being thrown. In that case you could use the ExceptionHandler annotation:
#ExceptionHandler(ResourceMovedPermanentlyException.class)
public String handleException(final Exception e) {
return "redirect:/the/target/page";
}
Alternative implementation:
#ExceptionHandler(ResourceMovedPermanentlyException.class)
public String handleException(final RuntimeException e,
final HttpServletRequest request, // this can be omitted if not needed
final HttpServletResponse response) throws IOException
response.sendRedirect("/the/page/or/url/you/need/to/redirect");
}
You also could store the redirect URL into the exception:
#ExceptionHandler(ResourceMovedPermanentlyException.class)
public String handleException(final ResourceMovedPermanentlyException e,
final HttpServletResponse response) throws IOException
response.sendRedirect(e.getRedirectUrl());
}
See the ExceptionHandler JavaDoc for more details about the options available for the exception handler method.

Resources