I am handling exceptions in spring using @ExceptionHandler. Any exception thrown by controller is caught using method annotated with @ExceptionHandler and action is taken ac
As the exception is not raised from a controller but a filter, @ControllerAdvice won't catch it.
So, the best solution i found after looking everywhere was to create a filter for this internal errors:
public class ExceptionHandlerFilter extends OncePerRequestFilter {
@Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} catch (JwtException e) {
setErrorResponse(HttpStatus.BAD_REQUEST, response, e);
e.printStackTrace();
} catch (RuntimeException e) {
e.printStackTrace();
setErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, response, e);
}
}
public void setErrorResponse(HttpStatus status, HttpServletResponse response, Throwable ex){
response.setStatus(status.value());
response.setContentType("application/json");
// A class used for errors
ApiError apiError = new ApiError(status, ex);
try {
String json = apiError.convertToJson();
System.out.println(json);
response.getWriter().write(json);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Add it to your config, i'm using a WebSecurityConfigurerAdapter implementation:
// Custom JWT based security filter
httpSecurity
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
// Custom Exception Filter for filter
httpSecurity
.addFilterBefore(exceptionHandlerFilterBean(), JwtAuthenticationTokenFilter.class);
The error class:
public class ApiError {
private HttpStatus status;
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss")
private LocalDateTime timestamp;
private String message;
private String debugMessage;
private ApiError() {
timestamp = LocalDateTime.now();
}
public ApiError(HttpStatus status) {
this();
this.status = status;
}
public ApiError(HttpStatus status, Throwable ex) {
this();
this.status = status;
this.message = "Unexpected error";
this.debugMessage = ex.getLocalizedMessage();
}
public ApiError(HttpStatus status, String message, Throwable ex) {
this();
this.status = status;
this.message = message;
this.debugMessage = ex.getLocalizedMessage();
}
public String convertToJson() throws JsonProcessingException {
if (this == null) {
return null;
}
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return mapper.writeValueAsString(this);
}
//Setters and getters
}
If, like me, you're stuck with spring 3.1 (just 0.1 vesrsions behind @ControllerAdvice
) you can try this solution I just came up with.
So, you've heard of exception resolvers, right? If not, read here:
@Component
public class RestExceptionResolver extends ExceptionHandlerExceptionResolver {
@Autowired
//If you have multiple handlers make this a list of handlers
private RestExceptionHandler restExceptionHandler;
/**
* This resolver needs to be injected because it is the easiest (maybe only) way of getting the configured MessageConverters
*/
@Resource
private ExceptionHandlerExceptionResolver defaultResolver;
@PostConstruct
public void afterPropertiesSet() {
setMessageConverters(defaultResolver.getMessageConverters());
setOrder(2); // The annotation @Order(2) does not work for this type of component
super.afterPropertiesSet();
}
@Override
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
ExceptionHandlerMethodResolver methodResolver = new ExceptionHandlerMethodResolver(restExceptionHandler.getClass());
Method method = methodResolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(restExceptionHandler, method);
}
return null;
}
public void setRestExceptionHandler(RestExceptionHandler restExceptionHandler) {
this.restExceptionHandler = restExceptionHandler;
}
public void setDefaultResolver(ExceptionHandlerExceptionResolver defaultResolver) {
this.defaultResolver = defaultResolver;
}
}
Then an example handler will look like this
@Component
public class RestExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
@ResponseBody
public Map<String, Object> handleException(ResourceNotFoundException e, HttpServletResponse response) {
Map<String, Object> error = new HashMap<>();
error.put("error", e.getMessage());
error.put("resource", e.getResource());
return error;
}
}
Of course you will not forget to register your beens
Then create a filter that is called before your desiered filter (optionally all of 'em)
Then in that filter
try{
chain.doFilter(request, response);
catch(Exception e){
exceptionResolver(request, response, exceptionHandler, e);
//Make the processing stop here...
return; //just in case
}
Check the below code snippet, it works for me.
final HttpServletResponseWrapper wrapper = new
HttpServletResponseWrapper((HttpServletResponse) res);
wrapper.sendError(HttpServletResponse.SC_UNAUTHORIZED, "<your error msg>");
res = wrapper.getResponse();
If you are using this inside a filter then add a return statement else chain.doFilter(req,res)
will override this.
Presumably, you want to set the HTTP Status code as a result of the exception being thrown in the Filter? If so, simply set the status as follows:
HttpServletResponse response = (HttpServletResponse) res; response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
Filters happens before controllers are even resolved so exceptions thrown from filters can't be caught by a Controller Advice.
Filters are a part of the servlet and not really the MVC stack.
I built my application with rest api, so I resolved this problem by catching it in the filter that may throw an exception and then writing something back. Remember that filterChain.doFilter(request, response);
must be included.
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
// something may throw an exception
filterChain.doFilter(request, response);
} catch (Exception e) {
// ResponseWrapper is a customized class
ResponseWrapper responseWrapper = new ResponseWrapper().fail().msg(e.getMessage());
response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
response.getWriter().write(JSON.toJSONString(responseWrapper));
}
}