Extracting HTTP Request Parameters in Spring Boot Interceptors

Reading HTTP request parameters within a Spring Boot HandlerInterceptor presents a speicfic challenge: the servlet input stream can only be consumed once. If a downstream controller or another filter reads the body, the interceptor will encounter an empty stream. To resolve this, the request must be wrapped to cache the payload, allowing multiple reads across the filter chain and interceptor stack.

Caching the Request Body

Extend HttpServletRequestWrapper to buffer the input stream in to memory. This implementation captures the raw bytes during initialization and serves them through a custom ServletInputStream on subsequent calls.

public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
    private final byte[] cachedPayload;

    public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
        super(request);
        this.cachedPayload = StreamUtils.copyToByteArray(request.getInputStream());
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        return new CachedServletInputStream(this.cachedPayload);
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8));
    }

    public String resolveCachedBody() {
        return new String(this.cachedPayload, StandardCharsets.UTF_8);
    }

    private static class CachedServletInputStream extends ServletInputStream {
        private final ByteArrayInputStream buffer;

        public CachedServletInputStream(byte[] contents) {
            this.buffer = new ByteArrayInputStream(contents);
        }

        @Override
        public int read() throws IOException {
            return buffer.read();
        }

        @Override
        public boolean isFinished() {
            return buffer.available() == 0;
        }

        @Override
        public boolean isReady() {
            return true;
        }

        @Override
        public void setReadListener(ReadListener listener) {
            throw new UnsupportedOperationException("Asynchronous reading not supported");
        }
    }
}

Applying the Wrapper via Filter

A servlet filter intercepts the encoming request before it reaches the dispatcher servlet. The filter conditionally wraps the request, bypassing multipart uploads to prevent interference with file handling.

@Component
public class RequestCachingFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String contentType = httpRequest.getContentType();

        // Skip wrapping for file uploads to avoid breaking multipart parsing
        if (contentType != null && contentType.toLowerCase().contains("multipart/")) {
            chain.doFilter(request, response);
        } else {
            chain.doFilter(new CachedBodyHttpServletRequest(httpRequest), response);
        }
    }
}

Interceptor with Parameter Extraction Strategy

The interceptor determines the payload format based on the Content-Type header and delegates extraction to a specific strategy. This separates form-urlencoded parsing from raw JSON retrieval.

public interface PayloadExtractor {
    String extract(HttpServletRequest request);
}

public class FormDataExtractor implements PayloadExtractor {
    @Override
    public String extract(HttpServletRequest request) {
        Map<String, String[]> paramMap = request.getParameterMap();
        return paramMap.entrySet().stream()
                .sorted(Map.Entry.comparingByKey())
                .map(entry -> entry.getKey() + "=" + String.join(",", entry.getValue()))
                .collect(Collectors.joining("&"));
    }
}

public class JsonBodyExtractor implements PayloadExtractor {
    @Override
    public String extract(HttpServletRequest request) {
        if (request instanceof CachedBodyHttpServletRequest) {
            return ((CachedBodyHttpServletRequest) request).resolveCachedBody();
        }
        return "";
    }
}

The interceptor orchestrates the extraction process during the preHandle phase.

@Component
public class ParameterInspectionInterceptor implements HandlerInterceptor {
    private final Map<String, PayloadExtractor> extractorRegistry = new HashMap<>();

    public ParameterInspectionInterceptor() {
        extractorRegistry.put("json", new JsonBodyExtractor());
        extractorRegistry.put("form", new FormDataExtractor());
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String contentType = request.getContentType();
        String strategyKey = (contentType != null && contentType.contains("application/json")) ? "json" : "form";

        PayloadExtractor extractor = extractorRegistry.get(strategyKey);
        String resolvedParams = extractor.extract(request);

        System.out.println("Intercepted Request Data: " + resolvedParams);
        return true;
    }
}

Component Registration

Register the filter and interceptor within a Spring configuration class. The filter is assigned the highest precedence to ensure the request is wrapped before any other component processes it.

@Configuration
public class WebMvcInfrastructureConfig implements WebMvcConfigurer {
    private final ParameterInspectionInterceptor inspectionInterceptor;

    public WebMvcInfrastructureConfig(ParameterInspectionInterceptor inspectionInterceptor) {
        this.inspectionInterceptor = inspectionInterceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(inspectionInterceptor)
                .addPathPatterns("/api/**")
                .excludePathPatterns("/api/public/**");
    }

    @Bean
    public FilterRegistrationBean<RequestCachingFilter> registerCachingFilter(RequestCachingFilter filter) {
        FilterRegistrationBean<RequestCachingFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(filter);
        registration.addUrlPatterns("/*");
        registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return registration;
    }
}

Tags: spring-boot java servlet-api interceptor Filter

Posted on Mon, 20 Jul 2026 16:39:11 +0000 by LiamH