Hello, fellow developers!
Recently, I encountered a challenging scenario while working with Spring's OncePerRequestFilter
and @ControllerAdvice
for exception handling. I'm excited to share both the problem I faced and the solution I discovered.
The Problem
In my project, where I extensively use @ControllerAdvice
to handle exceptions, I integrated a OncePerRequestFilter for request filtering. Initially, I assumed the exceptions thrown in the filter would be neatly caught by my controller advice. However, that wasn't the case.
Here's how my CustomFilter looked initially:
@Slf4j
public class CustomFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String trackingId = (String) request.getAttribute(Constants.TRACKING_ID);
ContentRequestWrapper requestWrapper = new ContentRequestWrapper(request);
try {
filterChain.doFilter(requestWrapper, response);
} catch (RuntimeException e) {
log.error("Error on method [doFilterInternal]: {} {} Tracking-ID: {}",
e.getClass(), e.getMessage(), trackingId, e);
throw new RuntimeException("Message");
} catch (Exception e) {
log.error("Error on method [doFilterInternal] Exception: {} - Tracking ID {}",
e.getMessage(), trackingId, e);
}
}
}
And my ControllerExceptionHandler
looked like this:
@Slf4j
@ControllerAdvice
public class ControllerExceptionHandler {
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<StandardError> requestError(RuntimeException e, HttpServletRequest request) {
String trackingId = request.getAttribute(Constants.TRACKING_ID).toString();
logger.error("Service Error - TrackingId: {}", trackingId, e);
var error = StandardError.builder()
.trackingId(trackingId)
.timestamp(LocalDateTime.now())
.path(request.getRequestURI())
.error("Service Error")
.status(SERVICE_UNAVAILABLE.value())
.message(e.getMessage())
.build();
return ResponseEntity.status(SERVICE_UNAVAILABLE).body(error);
}
}
Unfortunately, exceptions thrown in CustomFilter were not being handled by ControllerExceptionHandler
as expected.
Finding the Solution
After some research and experimentation, I found a valuable post on Stack Overflow that addressed a similar issue. It suggested integrating the HandlerExceptionResolver.
Here’s how I modified my CustomFilter:
@Slf4j
@Component("customRequestFilter")
public class CustomFilter extends OncePerRequestFilter {
private final HandlerExceptionResolver resolver;
public CustomFilter(@Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver) {
this.resolver = resolver;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String trackingId = (String) request.getAttribute(Constants.TRACKING_ID);
log.info("Method [doFilterInternal] checking authorization: tracking-id {}", trackingId);
ContentRequestWrapper requestWrapper = new ContentRequestWrapper(request);
try {
filterChain.doFilter(requestWrapper, response);
} catch (RuntimeException e) {
log.error("Error on method [doFilterInternal]: {} {} Tracking-ID: {}",
e.getClass(), e.getMessage(), trackingId, e);
resolver.resolveException(request, response, null, e);
} catch (Exception e) {
log.error("Error on method [doFilterInternal] Exception: {} - Tracking ID {}",
e.getMessage(), trackingId, e);
}
}
}
Additionally, I needed to register CustomFilter with Spring's context:
@Bean(name = "customFilter")
public FilterRegistrationBean<CustomFilter> basicAuthFilterFilter() {
final FilterRegistrationBean<CustomFilter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(customFilter);
filterRegistrationBean.addUrlPatterns("/subscription/*");
filterRegistrationBean.setName("CustomFilter");
return filterRegistrationBean;
}
Conclusion
By integrating HandlerExceptionResolver
into CustomFilter and utilizing resolver.resolveException() within the catch blocks, I was able to ensure that exceptions thrown in the filter were handled by ControllerAdvice. This approach may not be suitable for every situation, but it worked effectively for my specific needs.
I hope this breakdown helps you understand how to better manage exceptions in your Spring applications. If you face a similar problem, perhaps this solution will be helpful!
Thank you for reading, and happy coding!
Top comments (2)
Very nice. I did know this problem. The problem is that the call to filter is behind the call for your controller. So, when thrown, the filter caller did not send the exception to controllerAdvice. Nice to know the solution. Thanks.
Thanks Alex for your comment.