Spring restful #ExceptionHandler has too high priority/order - java

I have an #ExceptionHandler annotated method in a #ControllerAdvice annotated class.
This method must catch all exceptions, but after some default handlers have handled some exceptions, not as first handler (for example, it should not handle org.springframework.security.access.AccessDeniedException, otherwise the forwarding to the login page, which is done by Spring, does not work anymore).
I've found a lot of stuff but either old (not using #ControllerAdvice) or with Interfaces that cannot be used, because both html and JSON must fit to the return type of the method signature (using the class ResponseEntity, not some ModelAndView or String).
My current exception handler test method, which works fine:
#ExceptionHandler(Exception.class)
public ResponseEntity<?> handleExceptions(Exception ex, HttpServletRequest request) throws Exception {
HttpHeaders headers = new HttpHeaders();
//sort MIME types from accept header field
String accept = request.getHeader("accept");
if(accept != null) {
String[] mimes = accept.split(",");
//sort most quality first
Arrays.sort(mimes, new MimeQualityComparator());
//if json, return as json
String firstMime = mimes[0].split(";")[0];
if (firstMime.equals("application/json")) {
ExceptionWrapper exceptionVO = new ExceptionWrapper(ex);
headers.setContentType(MediaType.APPLICATION_JSON);
return new ResponseEntity<ExceptionWrapper>(exceptionVO, headers, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
//if not json, return as html
headers.setContentType(MediaType.TEXT_HTML);
String error = "<h1>Internal Server Error 500</h1><br>";
error += "Please copy the following text and send it to your server admin:<br><br>";
error += "<pre>" + ExceptionUtils.getStackTrace(ex) + "</pre>";
return new ResponseEntity<String>(error, headers, HttpStatus.INTERNAL_SERVER_ERROR);
}
It basically just decides in which format to return the error.
How can i lower the priority of this exception handler while still maintaining the Spring default exception handlers? Do i need to add some fancy Java-Config?
I already tried to create a class which implements ResponseEntityExceptionHandler and have overridden the handleExceptionInternal method there, which did not work. Spring complained it could not find a HttpStatus object to pass into the overridden method (ive tried providing that as bean, too).
I also tried using #Order on my #ControllerAdvice which changed nothing.
Spring Version 4.0.9

Related

Spring REST add field to 404 resonse code

Using latest Spring Boot as of May 2018. I've created a 404 response like this.
#ResponseStatus(HttpStatus.NOT_FOUND)
public class NotFoundException extends RuntimeException {
private final int errorId;
public NotFoundException(String errorMsg) {
super("-1," + errorMsg);
this.errorId = -1;
}
public NotFoundException(int errorId, String errorMsg) {
super(errorId + "," + errorMsg);
this.errorId = errorId;
}
public int getErrorId() {
return errorId;
}
}
The annotation #ResponseStatus(HttpStatus.NOT_FOUND) makes my NotFoundException appear like a 404 reponse like this
{
"timestamp":1527751944754,
"status":404,
"error":"Not Found",
"exception":"com.myapp.exception.NotFoundException",
"message":"1000,Could not find data for owner: 1234","path":"/resource/owner/1234"
}
I hoped that property "getErrorId" would appear in the response automatically, like this
{
"timestamp":1527751944754,
"status":404,
"error":"Not Found",
"exception":"com.myapp.exception.NotFoundException",
"message":"Could not find data for owner: 1234","path":"/resource/owner/1234",
"errorId": 1000
}
Is the a simply way (like an annotiation to the getErrorId method) of having the property "errorId" in the response?
You use #ControllerAdvice and #ExceptionHanlder in Spring. that is exception controller. In fact, you will make custom exception controller and define exception.
This is sample code for you :
#ControllerAdvice("your.package")
public class CommonExceptionHandler {
#ExceptionHandler(value = NoHandlerFoundException.class)
#ResponseStatus(value = HttpStatus.NOT_FOUND)
public #ResponseBody ResponseEntity<?> setNotFoundException(Exception exception) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
// this is sample map. you will make your custom model and you use exception parameter.
Map<String, String> map = new HashMap<String, String>();
map.put("timestamp", String.valueOf(new Date().getTime()));
map.put("status", HttpStatus.NOT_FOUND.toString());
map.put("error", "Not Found");
map.put("exception", exception.getMessage());
map.put("message", "Could not find data for owner: 1234");
map.put("path", "/resource/owner/1234");
map.put("errorId", "1000");
String json = mapper.writeValueAsString(map);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(json);
}
}
what ever Byeon0gam told everything is fine, here i am going to show another way means little bit of difference in maintaining code.
We know already ,
we can handle exceptions in spring-rest by 4 ways:
1. Using ResponseEntity Class.
2. Using #ResponseStatus Annotation.
3. Using #ExceptionHandler() Annotation.
4. Return Error Representation instead of default HTML error Page.
By using Those we can handle Exceptions at Method or Class level only.
But, if you want to handle Globally means throughout application , please follow below steps.
Handling Global Exception:
To Handle all Exceptions in our applications ,
First we need to create a class, after we need to use #ControllerAdvice Annotation on top of a class. In that class body , we can handle the exceptions raised in our application.
In that Class , we will create Exception handling methods , on top of every method we will use #ExceptionHandler() annotation for navigating Exceptions and for Handling .
If any exception raises in our application , based on #ExceptionHandler(“argument”) annotation argument the exception hadling method will be invoked and remaining handling code will be excuted.
#ControllerAdvice
public class SpringRestGlobalExceptionHandler {
#ExceptionHandler(Exception.class)
public ResponseEntity<?> exceptionHandler(HttpServletRequest req, Exception e)
{
JSONObject obj =new JSONObject();
obj.put("msgTxt","Unknown Server Error, Please Contact Admin." );
obj.put("reqUrl", req.getRequestURI());
obj.put("stackTrace", e.toString());
obj.put("isErrorFlag", true);
obj.put("httpStatusCode", HttpStatus.OK.value());
gstcDaoi.saveExceptionOrErrorLog(prepareTGstcExceptionOrErrorLogObject(obj));
e.printStackTrace();
return new ResponseEntity<>(obj, HttpStatus.OK);
}

#ControllerAdvice doesn't handle exceptions

I am using Spring Boot 1.5.9 for developing my application. I need implement jwt authentication, and I used jjwt library. The following code is from my custom authentication security filter which inherits from OncePerRequestFilter. Here I tried to parse the username from token, when username is parsing automatically is jwt verified and also check expiration of token. I debug it and it works, so I next want to send the correct message to the client app why authentication failed. I want to throw an ExpiredJwtException and handle it with the controller advice where I format the output.
Here is exception throwing:
try {
username = jwtTokenService.getUsername(authToken);
} catch (IllegalArgumentException e) {
logger.error("an error occured during getting username from token", e);
} catch (ExpiredJwtException e) {
logger.warn("the token is expired and not valid anymore", e);
throw new ExpiredJwtException(e.getHeader(), e.getClaims(), e.getMessage());
}
And here is my controller Advice, JwtException is base class of ExpiredJwtException which I throw so it should work. I also tried directly use ExpiredJwtException in ExceptionHandler, but didn't work as well. Next I want to handle another exceptions with same way.
#ControllerAdvice
public class GlobalControllerExceptionHandler {
#ExceptionHandler(Exception.class)
public #ResponseBody
ResponseEntity<Map<String, Object>> handleException(Exception ex) {
Map<String, Object> errorInfo = new HashMap<>();
errorInfo.put("message", ex.getMessage());
errorInfo.put("status", HttpStatus.BAD_REQUEST);
errorInfo.put("status_code", HttpStatus.BAD_REQUEST.value());
return new ResponseEntity<>(errorInfo, HttpStatus.BAD_REQUEST);
}
#ExceptionHandler(JwtException.class)
//#ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
public #ResponseBody
ResponseEntity handleJwtException(JwtException ex) {
Map<String, Object> errorInfo = new HashMap<>();
errorInfo.put("message", ex.getLocalizedMessage());
errorInfo.put("status", HttpStatus.UNPROCESSABLE_ENTITY);
errorInfo.put("status_code", HttpStatus.UNPROCESSABLE_ENTITY.value());
return new ResponseEntity<>(errorInfo, HttpStatus.UNPROCESSABLE_ENTITY);
}
}
Here is my folder structure:
I want return just response with 4xx status, but I always got 5xx Internal error when my exception is thrown. Can you tell me what is wrong with my code? Thanks in advice.
If the exception is thrown in filter, Springs exception handling (#ControllerAdvice, #ExceptionHandler) is not involved.
You need to catch all exceptions inside filter and work directly with ServletResponse.
As I understand - Filters are low level logic (request handling before spring infrastructure), but you can have a workaround, like a specific filter that wraps chaining and catches all RuntimeExceptions. (Looks like a crunch, but no other solutions).
If you want to have a specific login to create your exception object - override ErrorAttributes bean. It will allow you to have a single view for all application exceptions.
To directly specify http response status usehttpServletResponse.setStatus(... your status code ...);
Have your controller extend ResponseEntityExceptionHandler and have your exception handling methods take in the WebRequest as a parameter
Then change your return value to this
return handleExceptionInternal(ex, errorInfo, new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
The HttpStatus.BAD_REQUEST can be changed to any 40x error
Example for Exception.class
#ExceptionHandler(value = { Exception.class })
protected ResponseEntity<Object> handleUncaughtException(Exception ex, WebRequest request) {
String message = "Something bad happened";
return handleExceptionInternal(ex, message, new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
}
According to this Make simple servlet filter work with #ControllerAdvice you can create a custom handler.
Then add your new handler to your WebSecurityConfigurerAdapter
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CustomHandler());
}
I also faced this issue in which RestControllerAdivce was not handling the exception, Thing is that advice method can have only those arguments in its signature which exception throwing method have or can provide. My AOP method was not having access to Headers so it could not provide Headers to RestControllerAdivce method. As soon as I created a new exception handler method in RestController without Headers as argument, RestControllerAdivce started working as expected. Detials here

How to parse a URL and run a method with Spring MVC 'reflectively'?

I have a Spring Boot application that uses Spring MVC in the usual manner, with a bunch of #RequestMapping methods, Freemarker definitions, and the like. This is all tied together with a WebMvcConfigurerAdapter class.
I'd like to provide a service where the user submits a list of valid URLs, and the webapp would work out which controller would be called, passes in the parameters, and returns a combined result for every URL — all in one request.
This would save the user from having to make hundreds of HTTP calls, but would still allow them to make one-off requests if need be. Ideally, I'd just inject an auto-configured Spring bean, so I don't have to repeat the URL resolving and adapting and handling that Spring does internally, and the controller's list of other controllers would never go out of sync with the real list of controllers.
I expected to write something like this (simplified to only deal with one URL, which is pointless but easier to understand):
#Autowired BeanThatSolvesAllMyProblems allMappings;
#PostMapping(path = "/encode", consumes = MediaType.TEXT_PLAIN_VALUE)
#ResponseBody
public String encode(#RequestBody String inputPath) {
if (allMappings.hasMappingForPath(inputPath)) {
return allMappings.getMapping(inputPath).execute();
} else {
return "URL didn't match, sorry";
}
}
Instead, I've had to define Spring beans I don't know what they do and have been repeating some of what Spring is meant to do for me, which I'm worried won't work quite the same as it would if the user just made the call themselves:
// these two are #Beans, with just their default constructor called.
#Autowired RequestMappingHandlerMapping handlers;
#Autowired RequestMappingHandlerAdapter adapter;
#PostMapping(path = "/encode", consumes = MediaType.TEXT_PLAIN_VALUE)
#ResponseBody
public String encode(#RequestBody String inputText) {
final HttpServletRequest mockRequest = new MockHttpServletRequest(null, inputText);
final StringBuilder result = new StringBuilder();
this.handlers.getHandlerMethods().forEach((requestMappingInfo, handlerMethod) -> {
if (requestMappingInfo.getPatternsCondition().getMatchingCondition(mockRequest) != null) {
try {
final MockHttpServletResponse mockResponse = new MockHttpServletResponse();
result.append("Result: ").append(adapter.handle(mockRequest, mockResponse, handlerMethod));
result.append(", ").append(mockResponse.getContentAsString());
result.append("\n");
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
});
return result.toString();
}
I thought I was doing quite well going down this path, but it's failing with Missing URI template variable errors, and not only do I have no idea how to put the request parameters in (another thing which Spring could be able to handle itself), but I'm not even sure that this is the right way to go about doing this. So how do I simulate a Spring MVC request "reflectively", from within the webapp itself?
JSON API spec. solves this problem by allowing sending multiple operations per request. There even exists a quite mature implementation that supports this feature which is called Elide. But I guess this is might not fully meet your requirements.
Anyway, here's what you can do.
You have to take into consideration that DispatcherServlet holds handlerMappings list that is used to detect appropriate request handler and handlerAdaptors. The selection strategy for both lists is configurable (see DispatcherServlet#initHandlerMappings and #initHandlerAdapters).
You should work out a way you would prefer to retrieve this lists of handlerMappings/initHandlerAdapters and stay in sync with DispatcherServlet.
After that you can implement your own HandlerMapping/HandlerAdaptor (or present a Controller method as in your example) that would handle the request to /encode path.
Btw, HandlerMapping as javadoc says is
Interface to be implemented by objects that define a mapping between
requests and handler objects
or simply saying if we take DefaultAnnotationHandlerMapping that would map our HttpServletRequests to #Controller methods annotated with #RequestMapping. Having this mapping HandlerAdapter prepares incoming request to consuming controller method, f.ex. extracting request params, body and using them to call controller's method.
Having this, you can extract URLs from main request, create a list of stub HttpRequests holding the information needed for further processing and loop through them calling this:
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
having a handlerMapping you call
HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
if (ha.supports(handler)) {
return ha;
}
}
and then you can finally call
ha.handle(processedRequest, response, mappedHandler.getHandler());
which in turn would execute the controller's method with params.
But having all this, I would not recommend to following this approach, instead, think about usage of JSON API spec or any other.
How about using Springs RestTemplate as client for this? You could call your controllers within the spring controller as if it would be an external resource:
#ResponseBody
public List<String> encode(#RequestBody List inputPaths) {
List<String> response = new ArrayList<>(inputPaths.size());
for (Object inputPathObj : inputPaths) {
String inputPath = (String) inputPathObj;
try {
RequestEntity.BodyBuilder requestBodyBuilder = RequestEntity.method(HttpMethod.GET, new URI(inputPath)); // change to appropriate HttpMethod, maybe some mapping?
// add headers and stuff....
final RequestEntity<Void> requestEntity = requestBodyBuilder.build(); // when you have a request body change Void to e.g. String
ResponseEntity<String> responseEntity = null;
try {
responseEntity = restTemplate.exchange(requestEntity, String.class);
} catch (final HttpClientErrorException ex) {
// add your exception handling here, e.g.
responseEntity = new ResponseEntity<>(ex.getResponseHeaders(), ex.getStatusCode());
throw ex;
} finally {
response.add(responseEntity.getBody());
}
} catch (URISyntaxException e) {
// exception handling here
}
}
return response;
}
Note that generic do not work for the #RequestBody inputPaths.
See alse http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html and https://spring.io/guides/gs/consuming-rest/ .
I agree with the other answers that you should consider this feature outside of your project, instead of having it in the code. It is a question of design and you can choose the approach you want. Based on your comment that these are GET requests, you can achieve what you want with a request dispatcher to trigger your requests within your special Controller service method for each URL and capture the response with a HttpServletResponseWrapper instance.
In the following code sample, the "consolidate" method takes comma separated URLs like this ("http://localhost:8080/index/index1,index2", here "index1,index2" is the URL list), consolidates their text output into a single payload and returns it. For this example URL, the consolidated outputs of http://localhost:8080/index1 and http://localhost:8080/index2 will be returned. You might want to extend/modify this with added parameters, validation, etc for the URLs. I tested this code with Spring Boot 1.2.x.
#Controller
public class MyController {
#RequestMapping("/index/{urls}")
#ResponseBody
String consolidate(#PathVariable String[] urls, HttpServletRequest request, HttpServletResponse response) {
StringBuilder responseBody = new StringBuilder();
//iterate for each URL provided
for (String url : urls) {
RequestDispatcher dispatcher = request.getServletContext().getRequestDispatcher("/" + url);
HttpServletResponseWrapper wrapper = new HttpServletResponseWrapper((HttpServletResponse) response) {
private CharArrayWriter output = new CharArrayWriter();
#Override
public PrintWriter getWriter() {
return new PrintWriter(output);
}
#Override
public String toString() {
return output.toString();
}
};
try {
dispatcher.include(request, wrapper);
//append the response text
responseBody.append(wrapper.toString());
} catch (ServletException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//This holds the consolidated output
return responseBody.toString();
}
#RequestMapping("/index1")
String index1() {
return "index1";
}
#RequestMapping("/index2")
String index2() {
return "index2";
}
}

How to handle exceptions in Spring MVC differently for HTML and JSON requests

I'm using the following exception handler in Spring 4.0.3 to intercept exceptions and display a custom error page to the user:
#ControllerAdvice
public class ExceptionHandlerController
{
#ExceptionHandler(value = Exception.class)
public ModelAndView handleError(HttpServletRequest request, Exception e)
{
ModelAndView mav = new ModelAndView("/errors/500"));
mav.addObject("exception", e);
return mav;
}
}
But now I want a different handling for JSON requests so I get JSON error responses for this kind of requests when an exception occurred. Currently the above code is also triggered by JSON requests (Using an Accept: application/json header) and the JavaScript client doesn't like the HTML response.
How can I handle exceptions differently for HTML and JSON requests?
The ControllerAdvice annotation has an element/attribute called basePackage which can be set to determine which packages it should scan for Controllers and apply the advices. So, what you can do is to separate those Controllers handling normal requests and those handling AJAX requests into different packages then write 2 Exception Handling Controllers with appropriate ControllerAdvice annotations. For example:
#ControllerAdvice("com.acme.webapp.ajaxcontrollers")
public class AjaxExceptionHandlingController {
...
#ControllerAdvice("com.acme.webapp.controllers")
public class ExceptionHandlingController {
The best way to do this (especially in servlet 3) is to register an error page with the container, and use that to call a Spring #Controller. That way you get to handle different response types in a standard Spring MVC way (e.g. using #RequestMapping with produces=... for your machine clients).
I see from your other question that you are using Spring Boot. If you upgrade to a snapshot (1.1 or better in other words) you get this behaviour out of the box (see BasicErrorController). If you want to override it you just need to map the /error path to your own #Controller.
As you have the HttpServletRequest, you should be able to get the request "Accept" header. Then you could process the exception based on it.
Something like:
String header = request.getHeader("Accept");
if(header != null && header.equals("application/json")) {
// Process JSON exception
} else {
ModelAndView mav = new ModelAndView("/errors/500"));
mav.addObject("exception", e);
return mav;
}
Since i didn't find any solution for this, i wrote some code that manually checks the accept header of the request to determine the format. I then check if the user is logged in and either send the complete stacktrace if he is or a short error message.
I use ResponseEntity to be able to return both JSON or HTML like here.
Code:
#ExceptionHandler(Exception.class)
public ResponseEntity<?> handleExceptions(Exception ex, HttpServletRequest request) throws Exception {
final HttpHeaders headers = new HttpHeaders();
Object answer; // String if HTML, any object if JSON
if(jsonHasPriority(request.getHeader("accept"))) {
logger.info("Returning exception to client as json object");
headers.setContentType(MediaType.APPLICATION_JSON);
answer = errorJson(ex, isUserLoggedIn());
} else {
logger.info("Returning exception to client as html page");
headers.setContentType(MediaType.TEXT_HTML);
answer = errorHtml(ex, isUserLoggedIn());
}
final HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
return new ResponseEntity<>(answer, headers, status);
}
private String errorHtml(Exception e, boolean isUserLoggedIn) {
String error = // html code with exception information here
return error;
}
private Object errorJson(Exception e, boolean isUserLoggedIn) {
// return error wrapper object which will be converted to json
return null;
}
/**
* #param acceptString - HTTP accept header field, format according to HTTP spec:
* "mime1;quality1,mime2;quality2,mime3,mime4,..." (quality is optional)
* #return true only if json is the MIME type with highest quality of all specified MIME types.
*/
private boolean jsonHasPriority(String acceptString) {
if (acceptString != null) {
final String[] mimes = acceptString.split(",");
Arrays.sort(mimes, new MimeQualityComparator());
final String firstMime = mimes[0].split(";")[0];
return firstMime.equals("application/json");
}
return false;
}
private static class MimeQualityComparator implements Comparator<String> {
#Override
public int compare(String mime1, String mime2) {
final double m1Quality = getQualityofMime(mime1);
final double m2Quality = getQualityofMime(mime2);
return Double.compare(m1Quality, m2Quality) * -1;
}
}
/**
* #param mimeAndQuality - "mime;quality" pair from the accept header of a HTTP request,
* according to HTTP spec (missing mimeQuality means quality = 1).
* #return quality of this pair according to HTTP spec.
*/
private static Double getQualityofMime(String mimeAndQuality) {
//split off quality factor
final String[] mime = mimeAndQuality.split(";");
if (mime.length <= 1) {
return 1.0;
} else {
final String quality = mime[1].split("=")[1];
return Double.parseDouble(quality);
}
}
The trick is to have a REST controller with two mappings, one of which specifies "text/html" and returns a valid HTML source. The example below, which was tested in Spring Boot 2.0, assumes the existence of a separate template named "error.html".
#RestController
public class CustomErrorController implements ErrorController {
#Autowired
private ErrorAttributes errorAttributes;
private Map<String,Object> getErrorAttributes( HttpServletRequest request ) {
WebRequest webRequest = new ServletWebRequest(request);
boolean includeStacktrace = false;
return errorAttributes.getErrorAttributes(webRequest,includeStacktrace);
}
#GetMapping(value="/error", produces="text/html")
ModelAndView errorHtml(HttpServletRequest request) {
return new ModelAndView("error.html",getErrorAttributes(request));
}
#GetMapping(value="/error")
Map<String,Object> error(HttpServletRequest request) {
return getErrorAttributes(request);
}
#Override public String getErrorPath() { return "/error"; }
}
References
ModelAndView -- return type for HTML
DefaultErrorAttributes -- data used to render HTML template (and JSON response)
BasicErrorController.java -- Spring Boot source from which this example was derived
The controlleradvice annotation has several properties that can be set, since spring 4. You can define multiple controller advices applying different rules.
One property is "annotations. Probably you can use a specific annotation on the json request mapping or you might find another property more usefull?
Use #ControllerAdvice
Let the exception handler send a DTO containing the field errors.
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public ValidationErrorDTO processValidationError(MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
List<FieldError> fieldErrors = result.getFieldErrors();
return processFieldErrors(fieldErrors);
}
This code is of this website:http://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-adding-validation-to-a-rest-api/
Look there for more info.

SpringMVC handle errors in rest controller

I'm using SpringMVC and I want to handle exception on rest controller.
My controller usually write a json in response output, but when exception occurs I'm unable to catch it and tomcat html page is returned.
How I can catch global exceptions and return appropriate response based on "accept" parameter in request?
The #ControllerAdvice annotation is a new annotation that was added in the Spring 3.2 release. From the reference docs:
Classes annotated with #ControllerAdvice can contain #ExceptionHandler, #InitBinder, and #ModelAttribute methods and those will apply to #RequestMapping methods across controller hierarchies as opposed to the controller hierarchy within which they are declared. #ControllerAdvice is a component annotation allowing implementation classes to be auto-detected through classpath scanning.
Example:
#ControllerAdvice
class GlobalControllerExceptionHandler {
// Basic example
#ExceptionHandler
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
ErrorMessage handleException(FirstException ex) {
ErrorMessage errorMessage = createErrorMessage(ex);
return errorMessage;
}
// Multiple exceptions can be handled
#ExceptionHandler({SecondException.class, ThirdException.class})
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
ErrorMessage handleException() {
ErrorMessage errorMessage = createErrorMessage(...);
return errorMessage;
}
// Returning a custom response entity
#ExceptionHandler
ResponseEntity<ErrorMessage> handleException(OtherException ex) {
ErrorMessage errorMessage = createErrorMessage(...);
ResponseEntity<ErrorMessage> responseEntity = new ResponseEntity<ErrorMessage>(errorMessage, HttpStatus.BAD_REQUEST);
return responseEntity;
}
}
Basically, it allows you to catch the specified exceptions, creates a custom ErrorMessage (this is your custom error class that Spring will serialize to the response body according to the Accept header) and in this example sets the response status to 400 - Bad Request. Note that the last example returns a ResponseEntity (and is not annotated with #ResponseBody) which allows you to specify response status and other response headers programmatically. More information about the #ExceptionHandler can be found in the reference docs, or in a blog post that I wrote some time ago.
Update: added more examples based on comments.
Another approach (what I'm using) is to create a global exception handler and tell Spring that it should be used. Then you don't have to duplicate your logic or to extend the same base-controller as you have to do when annotating a controller method with #ExceptionHandler. Here's a simple example.
public class ExceptionHandler implements HandlerExceptionResolver {
#Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse response, Object o, Exception e) {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); // Or some other error code
ModelAndView mav = new ModelAndView(new MappingJackson2JsonView());
mav.addObject("error", "Something went wrong: \"" + e.getMessage() + "\"");
return mav;
}
}
And in the <something>-servlet.xml you assign it as your wanted exceptionResolver:
<!-- Define our exceptionHandler as the resolver for our program -->
<bean id="exceptionResolver" class="tld.something.ExceptionHandler" />
Then all exceptions will be sent to your Exceptionhandler, and there you can take a look at the request and determine how you should reply to the user. In my case I'm using Jackson.
the ExceptionHandler annotation is doing hwat you want.

Categories

Resources