Backend Development 9 min read

Enabling Multiple @RequestBody Parameters in Spring MVC by Caching the Request Body

This article explains why Spring MVC cannot parse multiple @RequestBody annotations on a single handler method, analyzes the internal I/O stream closure that causes the failure, and provides a practical solution using a request‑body caching wrapper and a servlet filter to allow repeated reads of the request payload.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Enabling Multiple @RequestBody Parameters in Spring MVC by Caching the Request Body

Spring MVC normally rejects the use of more than one @RequestBody annotation on a single controller method because the underlying input stream is consumed and closed after the first read, leaving the second parameter with a null body and resulting in a 400 error.

The root cause lies in EmptyBodyCheckingHttpInputMessage which, after the first invocation of readWithMessageConverters , sets the internal body field to null as the I/O stream has already been exhausted. Consequently, the second @RequestBody cannot be resolved.

To overcome this limitation, the article proposes caching the request payload so that it can be read multiple times. A custom HttpServletRequestWrapper named CacheRequestBodyContent reads the original input stream into a byte array, then overrides getReader and getInputStream to supply a fresh stream backed by the cached bytes.

public class CacheRequestBodyContent extends HttpServletRequestWrapper {
    private byte[] body = null;
    public CacheRequestBodyContent(HttpServletRequest request, ServletResponse response) {
        super(request);
        try {
            request.setCharacterEncoding("utf-8");
            response.setCharacterEncoding("utf-8");
            body = IoUtil.readBytes(request.getInputStream(), false);
        } catch (Exception e) {
            log.error("请求数据读取失败,请重试");
        }
    }
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }
    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream cache = new ByteArrayInputStream(body);
        return new ServletInputStream() {
            @Override
            public int read() throws IOException { return cache.read(); }
            @Override
            public int available() throws IOException { return body.length; }
            // ... other methods omitted
        };
    }
}

A servlet Filter called CacheRequestFilter wraps incoming HttpServletRequest objects with this wrapper, ensuring that downstream handlers receive the cached request body.

public class CacheRequestFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        ServletRequest requestWrapper = null;
        if (request instanceof HttpServletRequest) {
            requestWrapper = new CacheRequestBodyContent((HttpServletRequest) request, response);
        }
        if (requestWrapper == null) {
            chain.doFilter(request, response);
        } else {
            chain.doFilter(requestWrapper, response);
        }
    }
}

The filter is registered via a Spring configuration class, applying it to all URL patterns.

@Configuration
public class FilterConfig {
    @Bean
    public FilterRegistrationBean someFilterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new CacheRequestFilter());
        registration.addUrlPatterns("/*");
        registration.setName("requestFilter");
        registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
        return registration;
    }
}

After adding the wrapper and filter, the same endpoint (e.g., http://localhost:8080/users/duplicate ) successfully processes a controller method with two @RequestBody parameters, returning the combined DTO without errors, thereby solving the duplicate @RequestBody parsing issue in Spring MVC.

backendJavacachingHTTPSpring MVCfilterRequestBody
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.