Handling Exceptions in Spring with OncePerRequestFilter and @ControllerAdvice

andremoriya

André Moriya

Posted on March 21, 2024

Handling Exceptions in Spring with OncePerRequestFilter and @ControllerAdvice

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);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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!

💖 💪 🙅 🚩
andremoriya
André Moriya

Posted on March 21, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related