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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
