Spring MVC - ExceptionHandler - spring

I am using Spring Boot 1.5.7.
I have an ExceptionHandler for my exception that returns a ResponseEntity
#ExceptionHandler(MyException.class)
public ResponseEntity<ResponseExceptionEntity> handleException(MyException e) {
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(new ResponseExceptionEntity(e));
}
This works well in situations where the exception occurs during an api call returning a ResponseEntity / #ResponseBody (JSON/XML response)
I would like to return a ModelAndView in situations where the exception occurs during a request where HTML is returned. In my situation all Controllers are annotated with #Controller and not #RestController
how can I write a ExceptionHandler for both cases (api/html)?
Or How can I determine what the resolved view is for a controller method?
Or how can I determine what the return type of the Controller Method
is?
Or How can I determine what the resolved view is for the controller
method?
I've tried the suggestion in this answer, but it doesn't return a JSON response when the Controller Method is annotated with #ResponseBody.
https://stackoverflow.com/a/32071252/461055

Yes, you can write two exception handler to handle api and html request exception. Here is sample code to illustrate the solution.
#ControllerAdvice(annotations = RestController.class)
#Order(1)
class RestExceptionHandler {
#ExceptionHandler(MyException.class)
#ResponseBody
ResponseEntity<ErrorResponse> exceptionHandler() {
....
}
}
#ControllerAdvice(annotations = Controller.class)
#Order(2)
class ExceptionHandler {
#ExceptionHandler(Exception.class)
public ModelAndView handleError500(HttpServletRequest request, HttpServletResponse response, Exception ex) {
ModelAndView mav = new ModelAndView("error");
mav.addObject("error", "500");
return mav;
}
}

Related

How to get the updated/modified HttpServletRequest object from AOP #Before advice to Spring controller method

I used Spring AOP #Before advice in Spring boot application, and it should execute before hitting any api's.
My task/requirement :- If in the request header application-name is not passed then we should modify the header and add to 'unknown' value to the application-name for every API.
I am modifying the header in the AOP #before advice using HttpServletWrapper class as shown below.
Problem is - the AOP should return updated HttpServletrequest to a controller method but it's not working, not returning the updated one in controller.
Controller:-
#GetMapping
#RequestMapping("/demo")
public ResponseEntity<String> getEmployee(HttpServletRequest request) {
System.out.println("Header, application-name"+request.getHeader("application-name"));
return new ResponseEntity<>("Success", HttpStatus.OK);
}
Spring AOP code,
#Aspect
#Component
public class AOPExample {
#Pointcut("#annotation(org.springframework.web.bind.annotation.GetMapping) ||"
+ "#annotation(org.springframework.web.bind.annotation.PostMapping)")
public void controllerRequestMapping() {}
#Before("controllerRequestMapping()")
public HttpServletRequest advice(JoinPoint jp) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
String header = request.getHeader("application-name");
if (header == null) {
HttpServletRequestWrapperClass wrappedRequest = new HttpServletRequestWrapperClass(request);
wrappedRequest.putHeader("application-name", "Unknown");
request = wrappedRequest;
} else {
//validate application name
//400 - throw bad request exception
}
System.out.println("After setting---"+request.getHeader("application-name"));
return request;
}
}
Finally I resolved the issue,
Instead of using #Before advice used #Around advice, Around advice should return the object using proceed method.
#Aspect
#Component
public class AOPExample {
#Pointcut("#annotation(org.springframework.web.bind.annotation.GetMapping) ||"
+ "#annotation(org.springframework.web.bind.annotation.PostMapping)")
public void controllerRequestMapping() {}
#Around("controllerRequestMapping()")
public Object advice(ProceedingJoinPoint jp) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
String header = request.getHeader("application-name");
System.out.println("Header in AOP"+header);
if (header == null) {
HttpServletRequestWrapperClass wrappedRequest = new HttpServletRequestWrapperClass(request);
wrappedRequest.putHeader("application-name", "Unknown");
request = wrappedRequest;
} else {
//validate application name
//400 - throw bad request exception
//throw new BadRequestException("Invalid application name");
}
System.out.println("After setting---"+request.getHeader("application-name"));
return jp.proceed(new Object[] {request});
}
}
Updated httpservlet request is getting in controller method. Thanks

How to print the Controller method name in #ControllerAdvice?

I went through link: How to find controller name in #controllerAdvice or #RestControllerAdvice in Spring MVC? many times, but I am looking to get the Controller methodname in #ControllerAdvice class.
Here is my CustomHandler Class.
#ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
........
..........
..........
#ExceptionHandler({DataExistsException.class})
public ResponseEntity<Object> handleDataExistsException(RuntimeException e, WebRequest request, HttpServletRequest httpRequest, HttpServletResponse response, HandlerMethod handlerMethod) {
// Here I am looking to print Controller endpoint method name
LoggingUtil.printJsonReq(httpRequest, HttpStatus.valueOf("BAD_REQUEST").value(), LoggingUtil.getTime(httpRequest), null, response);
return handleExceptionInternal(e, getErrors(error), getHeaders(), HttpStatus.BAD_REQUEST, request);
}
}
HandlerMethod should give you this information. Quoting the documentation:
Encapsulates information about a handler method consisting of a method and a bean. Provides convenient access to method parameters, the method return value, method annotations, etc.
So you can use:
Method method = handlerMethod.getMethod();
you can find out the method name from the RuntimeException so that would be
e.getStackTrace()[0].getMethodName();

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.

Does ControllerAdvice increase response time?

Is there any speed difference when using ControllerAdvice throwing RuntimeException, and when manually returning ResponseEntity to handle client errors?
1) ControllerAdvice
#RestController
public class ObjectController {
#PostMapping
public Object save(#RequestBody Object object) {
if (service.isInvalid(object))
throw new ObjectException("Client error");
return service.save(object);
}
}
public class ObjectException extends RuntimeException {
}
#ControllerAdvice
public class ObjectControllerAdvice extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = {ObjectException.class})
protected ResponseEntity<Object> handleConflict(ObjectException ex, WebRequest request) {
return handleExceptionInternal(ex, ex.getLocalizedMessage(), new HttpHeaders(),
HttpStatus.BAD_REQUEST, request);
}
}
2) Manually returning ResponseEntity
#RestController
public class ObjectController {
#PostMapping
public ResponseEntity<Object> save(#RequestBody Object object) {
if (service.isInvalid(object))
return new ResponseEntity<>("Client error", HttpStatus.BAD_REQUEST);
return new ResponseEntity<Object>(service.save(object), HttpStatus.OK);
}
}
I imagine the difference is response time is negligible with the second approach possibly being very slightly faster. But the real advantage of having a #ControllerAdvice class with #ExceptionHandlers is that these can be used for multiple endpoints over multiple #Controllers and you won't have to repeat the code everywhere.
No, it's not that much different. And I think using the #ControllerAdvice is a best practice when you would like to handle your Custom Exception or to centralize the Exception to a Global class. There is a simple sample in this answer: Error page registrar and Global exception handling
Hope this help.

#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

Resources