Error Handling in (Spring) Servlet Filters

Featured image for sharing metadata for article

If you're using Java Servlet Filters, you've likely come to the situation where you need to fail the request, for instance if there's a mandatory parameter missing, or the request is otherwise deemed to be invalid.

Let's say that for instance you're using a filter for tracking the correlation-id header:

@Component
public class CorrelationIdFilter extends OncePerRequestFilter {
  @Override
  protected void doFilterInternal(
      @NonNull HttpServletRequest request,
      @NonNull HttpServletResponse response,
      @NonNull FilterChain filterChain)
      throws ServletException, IOException {

    String correlationId = request.getHeader("correlation-id");
    if (null == correlationId || !Patterns.UUID_V4.matcher(correlationId).matches()) {
      // only allow UUIDs, if it's not valid according to our contract, allow it to be rewritten
      // alternatively, we would reject the request with an HTTP 400 Bad Request, as a client
      // hasn't fulfilled the contract
      correlationId = UUID.randomUUID().toString();
    }
    // make sure that the Mapped Diagnostic Context (MDC) has the `correlationId` so it can then
    // be populated in the logs
    try (MDC.MDCCloseable closable = MDC.putCloseable("correlationId", correlationId)) {
      response.addHeader("correlation-id", correlationId); // so the response contains it, too
      filterChain.doFilter(request, response);
    }
  }
}

You may want to reach for something like this:

    if (null == correlationId || !Patterns.UUID_V4.matcher(correlationId).matches()) {
      throw new CorrelationIdMalformedException();
    }

Which can then have a corresponding exception handler:

@ControllerAdvice
public class GlobalExceptionHandler {

  @ExceptionHandler(CorrelationIdMalformedException.class)
  protected ResponseEntity<ErrorResponse> handleCorrelationIdMalformedException(
      CorrelationIdMalformedException ex, WebRequest request) {
    // i.e.
    ErrorResponse errorResponse = new ErrorResponse();
    errorResponse.setError(ErrorResponse.Error.INVALID_REQUEST);
    errorResponse.setErrorDescription(ex.getMessage());
    return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
  }
}

Unfortunately this is a common pitfall, and as noted in this conversation on StackOverflow is not possible because the Servlet Filter executes before Spring's own DispatchServlet, so we don't have the ability to do this.

Instead, we need to use the Servlet API to return the error, for instance using our handy new method handleInvalidCorrelationId:

@Component
public class CorrelationIdFilter extends OncePerRequestFilter {

  private final ObjectMapper objectMapper;

  public CorrelationIdFilter(ObjectMapper objectMapper) {
    this.objectMapper = objectMapper;
  }

  @Override
  protected void doFilterInternal(
      HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
      throws ServletException, IOException {
    String uuid = request.getHeader("correlation-id");
    if (uuid == null) {
      uuid = UUID.randomUUID().toString();
    }
    if (!uuid.matches(Patterns.UUID_STRING)) {
      handleInvalidCorrelationId(response);
      return; // make sure you have this set!
    }
    try {
      filterChain.doFilter(request, response);
    } finally {
      response.addHeader("correlation-id", uuid);
    }
  }

  private void handleInvalidCorrelationId(HttpServletResponse response) throws IOException {
    ErrorResponse errorResponse = new ErrorResponse();
    errorResponse.setError(ErrorResponse.Error.INVALID_REQUEST);
    errorResponse.setErrorDescription("The correlation-id is not a valid UUID.");

    response.setContentType("application/json");
    response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
    response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
  }
}

This will reject the request correctly, and allow us to specify the response's headers and body correctly.

Written by Jamie Tanna's profile image Jamie Tanna on , and last updated on .

Content for this article is shared under the terms of the Creative Commons Attribution Non Commercial Share Alike 4.0 International, and code is shared under the Apache License 2.0.

#blogumentation #java #spring-boot #spring.

This post was filed under articles.

Interactions with this post

Interactions with this post

Below you can find the interactions that this page has had using WebMention.

Have you written a response to this post? Let me know the URL:

Do you not have a website set up with WebMention capabilities? You can use Comment Parade.