Handling Exceptions in Spring with OncePerRequestFilter and @ControllerAdvice
Tags: springboot, java, community
Published: 2024-03-21 15:17:08

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!