I am handling exceptions in spring using @ExceptionHandler. Any exception thrown by controller is caught using method annotated with @ExceptionHandler and action is taken accordingly. To avoid writing @exceptionHandler for every controller i am using @ControllerAdvice annotation.
Everything works fine as expected.
Now i have a filter(Yes, not interceptor, to handle certain requirement) which is implemented using DelegatingFilterProxy and ContextLoaderListener.
When i am throwing the same exception from above filter, its not caught the way it was done in controller case. It is directly thrown to user.
What's wrong in here?
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.
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 you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With