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.
Related
I have a simple webservice that returns content either as json or as plain text (depending on the clients' accept http header).
Problem: if an error occurs during text/plain request, Spring somehow returns a 406 Not Acceptable. Which is kind of wrong, because spring could as well just write the error out as plain error text, and moreover should absolutely preserve the 400 error status:
#RestController
public class TestServlet {
#PostMapping(value = "/test", produces = {APPLICATION_JSON_VALUE, TEXT_PLAIN_VALUE, "text/csv"})
public Object post() {
throw new BadRequestException("bad req");
}
}
#ResponseStatus(HttpStatus.BAD_REQUEST)
public class BadRequestException extends RuntimeException {
public BadRequestException(String msg) {
super(msg);
}
}
POST request with accept=application/json:
{
"timestamp": "2018-07-30T14:26:02",
"status": 400,
"error": "Bad Request",
"message": "bad req",
"path": "/test"
}
BUT with accept=text/csv (or text/plain) shows an empty response with status 406 Not Acceptable.
I also noticed the DispatcherServlet.processDispatchResult() is called twice: first with my BadRequest exception, 2nd time with HttpMediaTypeNotAcceptableException. So clearly the rendering of my custom exception fails, but why?
The problem is the restrictive Accept header allowing only one content type as response. In case of an error, Spring MVC needs to handle the BadRequestException and produce the required content type using a registered HttpMessageConverter.
By default Spring Boot has no message converter to produce text/plain directly from any object. You may register an ObjectToStringHttpMessageConverter (as a bean should work for Spring Boot) to allow this and you will get the result of BadRequestException.toString() as response body.
I assume a similar problem for text/csv but I am not sure how your setup for CSV message conversion looks like.
The condition written in "produces" determines the media type to use for the response to be "text/csv". So For a success scenario it works fine, **
but when you go for rendering an exception with a JSON body that
becomes a problem and gives you a 406 instead.
**
in latest versions of spring framework the problem fixes, but in old versions,as mentioned in Spring JIRA comments you should remove HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE attribute from request
the code might be like this :
#RestControllerAdvice
public class ExampleControllerAdvice {
#ExceptionHandler(value = Exception.class)
public ResponseEntity<?> handleException(HttpServletRequest request, Exception e) {
request.removeAttribute(
HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
return new ResponseEntity<?>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
we can handle exception in advice
#ControllerAdvice
class ExceptionHandler{
#ExceptionHandler(value = {HttpMediaTypeNotAcceptableException.class})
public ResponseEntity handleMediaTypeException(HttpMediaTypeNotAcceptableException e) {
APIErrorResponse apiErrorResponse = new APIErrorResponse();
apiErrorResponse.setErrorCode("set custom code here");
apiErrorResponse.setErrorMessage("set custom meggage here/ here we can use message from object of exception i.e e.getMessage()");
return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
}
}
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);
}
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
I am passing a header to a spring REST api like:
#RequestHeader(value="test-header")
header is mandatory here for the API, so I do not want to keep it optional.
when no header is passed, any call to the API returns a standard 400 error indicating that request is syntantically wrong and then it does not enter the REST API. But, I want to construct a proper ResponseBody and return a json for this error. I am not sure about the best way to do this. I thought about using spring interceptor and check if this header was passed or not, but then I am not sure if I can create a responsebody from here. Atleast I could not figure out how to do so.
will interceptor approach work for this? If yes, how? If not, then what are the options? Can someone please help on this?
Update:
This is how the REST API is:
public void methodA(#RequestHeader(value="test-header") String header, #RequestBody User user, HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
...
...
}
When the header is present, it will enter the REST API and continue with the logic. But, if the header is not present, it does not enter the API and simply returns a standard 400 error.
The interceptor that I wrote is like:
public class XXXInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
...
...
...
return true;
}
}
STEP1: Use spring validator annotation like #valid to validate your request.
STEP 2: Write your custom validator class. which will be responsible to check the header and see if it has value or it has the expected value.
STEP 3: If the request is not correct validator throws your custom exception.
STEP 4: write your exception handler class. In the class define what response must me returned if the exception in STEP 3 is caught.
For more information on Exception Handling in Spring.
In our current projet we do use a java interceptor to authenticate the request but nothing beyound that.
Write a method with the annotation #ExceptionHandler and use ServletRequestBindingException.class as this exception is thrown in case of miss. You can return any type of object from this method.
For example
#ExceptionHandler(ServletRequestBindingException.class)
public ResponseEntity<ResponseObject> handleHeaderError(){
ResponseObject responseObject=new ResponseObject();
responseObject.setStatus(Constants.ResponseStatus.FAILURE.getStatus());
responseObject.setMessage(header_missing_message);
ResponseEntity<ResponseObject> responseEntity=new ResponseEntity<ResponseObject>(responseObject, HttpStatus.BAD_REQUEST);
return responseEntity;
}
Another approach would be using Spring Interceptors (HandlerInterceptorAdapter), as you mentioned in your question, with #ControllerAdvice and return your JSON in an #ExceptionHandler method.
Take a look at the following post: http://www.journaldev.com/2651/spring-mvc-exception-handling-exceptionhandler-controlleradvice-handlerexceptionresolver-json-response-example
This is coming late but then, a very straightforward way to deal with this type of issue is to use a Controller Advice class which allows you to handle exceptions across the whole application in one global handling component.
The exception throw by spring is the MissingRequestHeaderException which you can then provide a custom handler in your controller advice class.
#Slf4j
#RestControllerAdvice
public class ControllerAdvice {
#ExceptionHandler(MissingRequestHeaderException.class)
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public ErrorResponse handleMissingRequestHeaderException(MissingRequestHeaderException ex) {
log.error(ex.getMessage(), ex);
return new ErrorResponse("Missing request header: " + ex.getHeaderName());
}
}
public class ErrorResponse implements Serializable {
private String message;
public ErrorResponse(String message) {
this.message = message;
}
}
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