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
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
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();
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.
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.
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