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

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.

Related

Generic exception handler prevents specific exception being handled

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 .

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 vs #ControllerAdvice

What are the major difference between #RestControllerAdvice and #ControllerAdvice ??
Is it we should always use #RestControllerAdvice for rest services and #ControllerAdvice MVC ?
#RestControllerAdvice is just a syntactic sugar for #ControllerAdvice + #ResponseBody, you can look here.
Is it we should always use #RestControllerAdvice for rest services and
#ControllerAdvice MVC?
Again, as mentioned above, #ControllerAdvice can be used even for REST web services as well, but you need to additionally use #ResponseBody.
In addition, we can just understand it as:
#RestControler = #Controller + #ResponseBody
#RestControllerAdvice = #ControllerAdvice + #ResponseBody.
Keeping in mind that #RestControllerAdvice is more convenient annotation for handling Exception with RestfulApi.
Example os usage:
#RestControllerAdvice
public class WebRestControllerAdvice {
#ExceptionHandler(CustomNotFoundException.class)
public ResponseMsg handleNotFoundException(CustomNotFoundException ex) {
ResponseMsg responseMsg = new ResponseMsg(ex.getMessage());
return responseMsg;
}
}
In that case any exception instanceOf CustomNotFoundException will be thrown in body of response.
Example extracted here:
https://grokonez.com/spring-framework/spring-mvc/use-restcontrolleradvice-new-features-spring-framework-4-3
Exception: A good REST API should handle the exception properly and send the proper response to the user. The user should not be rendered with any unhandled exception.
A REST API developer will have two requirements related to error handling.
Common place for Error handling
Similar Error Response body with a proper HTTP status code across APIs
#RestControllerAdvice is the combination of both #ControllerAdvice and #ResponseBody
The #ControllerAdvice annotation was first introduced in Spring 3.2.
We can use the #ControllerAdvice annotation for handling exceptions in the RESTful Services but we need to add #ResponseBody separately.
Note:
GlobalExceptionHandler was annotated with #ControllerAdvice, thus it is going to intercept exceptions from controllers accross the application.
The differences between #RestControllerAdvice and #ControllerAdvice is :
#RestControllerAdvice = #ControllerAdvice + #ResponseBody. - we can
use in REST web services.
#ControllerAdvice - We can use in both MVC and Rest web services, need to
provide the ResponseBody if we use this in Rest web services.
For Example :
Exception Class:
#ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends Exception{
private static final long serialVersionUID = 1L;
public ResourceNotFoundException(String message){
super(message);
}
}
usage of the above exception in Rest Web Service.
#RestControllerAdvice
public class MyRestControllerAdviceHandler {
#ExceptionHandler(ResourceNotFoundException.class)
public ResponseMsg resourceNotFoundException(ResourceNotFoundException ex) {
ResponseMsg resMsg = new ResponseMsg(ex.getMessage());
return resMsg;
}
}
usage of the above exception in MVC.
#ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<?> resourceNotFoundException(ResourceNotFoundException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
}
}
If you use #ControllerAdvice and return your error object from a method then it will look for a view with the name of your error object so instead of returning the expected response it will return 404 for not founding a view page with that name
#ControllerAdvice
public class CustomizedExceptionHandler {
#ExceptionHandler({ UserNotFoundException.class })
#ResponseStatus(code = HttpStatus.BAD_REQUEST)
public ExceptionResponce handleUserNotException(Exception ex, WebRequest request) throws Exception {
ExceptionResponce exceptionResponce = new ExceptionResponce(new Date(), ex.getMessage(),
request.getDescription(false));
return exceptionResponce;
}
}
As in the above code, I want to return 400 (BAD_REQUEST) but
instead of 400, it is returning 404(NOT_FOUND)
You can solve this issue by using any of the below ways
add #ResponseBody to your method or class.
Use #RestControllerAdvice.
Or you can wrap your error object in ResponseEntity.
After using either of the above ways it returns the correct response

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.

Handling addtional exceptions and throwing custom error message in Spring security

I am using Spring 2.5 .I want to do the following:
In the overriden loadUserByUsername method of spring UserDetailsService I want to throw a custom exception and on that exception give a custom error message on the login page.
Could anyone suggest how can I handle this custom exception.
You can extend AbstractHandlerExceptionResolver and handle your exception in this class. Code is not full, but you can understand the idea from that part.
public class ExceptionResolver extends AbstractHandlerExceptionResolver {
#Override
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
ModelAndView mav;
if(ex instanceof YourException){
mav = loginController.showLoginPage();
mav.addObject("errorMessage","Some error message text.");
\\also you can change response code here or add some logic
}
else {
\\some another page
}
return mav;
}
Also add this to applicationContext.xml(or another file with your spring context configuration):
<bean class="com.mycompany.ExceptionResolver" />

Resources