How to Read the Request Body Multiple Times in Spring Boot 3
This guide explains why Spring MVC can read the request body only once and provides a custom solution using a RepeatReadBodyMethodProcessor and RepeatBodyHttpRequest to enable multiple @RequestBody parameters in Spring Boot 3.5.0.
Spring MVC reads the request body as a single InputStream, which is closed after the first read. Therefore a controller method cannot have multiple @RequestBody parameters by default.
Custom implementation to allow repeated reads
1. Custom argument resolver
public class RepeatReadBodyMethodProcessor extends RequestResponseBodyMethodProcessor {
public RepeatReadBodyMethodProcessor(List<HttpMessageConverter<?>> converters,
List<Object> requestResponseBodyAdvice) {
super(converters, requestResponseBodyAdvice);
}
@Override
protected ServletServerHttpRequest createInputMessage(NativeWebRequest webRequest) {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
Assert.state(servletRequest != null, "No HttpServletRequest");
return new RepeatBodyHttpRequest(servletRequest);
}
}2. Request wrapper that caches the body
public class RepeatBodyHttpRequest extends ServletServerHttpRequest {
private static final String REQ_BODY_CONTENT = "req_body_content";
private final HttpServletRequest request;
public RepeatBodyHttpRequest(HttpServletRequest request) {
super(request);
this.request = request;
}
@Override
public InputStream getBody() throws IOException {
byte[] body = (byte[]) this.request.getAttribute(REQ_BODY_CONTENT);
if (body != null) {
return new ByteArrayInputStream(body);
}
InputStream is = super.getBody();
body = StreamUtils.copyToByteArray(is);
this.request.setAttribute(REQ_BODY_CONTENT, body);
return new ByteArrayInputStream(body);
}
}3. Register the resolver with higher priority
Adding the resolver via WebMvcConfigurer#addArgumentResolvers gives it lower precedence than the default. Instead, modify the RequestMappingHandlerAdapter bean after construction and insert the custom processor before the existing RequestResponseBodyMethodProcessor instances.
@Component
public class ConfigArgumentProcessor {
private final RequestMappingHandlerAdapter requestMappingHandlerAdapter;
public ConfigArgumentProcessor(RequestMappingHandlerAdapter requestMappingHandlerAdapter) {
this.requestMappingHandlerAdapter = requestMappingHandlerAdapter;
}
@PostConstruct
private void init() {
List<HandlerMethodArgumentResolver> original = requestMappingHandlerAdapter.getArgumentResolvers();
List<HandlerMethodArgumentResolver> newResolvers = new ArrayList<>();
for (HandlerMethodArgumentResolver resolver : original) {
if (resolver instanceof RequestResponseBodyMethodProcessor) {
try {
Field convertersField = RequestMappingHandlerAdapter.class.getDeclaredField("messageConverters");
convertersField.setAccessible(true);
List<HttpMessageConverter<?>> converters = (List<HttpMessageConverter<?>>) convertersField.get(requestMappingHandlerAdapter);
Field adviceField = RequestMappingHandlerAdapter.class.getDeclaredField("requestResponseBodyAdvice");
adviceField.setAccessible(true);
List<Object> advice = (List<Object>) adviceField.get(requestMappingHandlerAdapter);
newResolvers.add(new RepeatReadBodyMethodProcessor(converters, advice));
} catch (Exception e) {
// handle reflection errors if needed
}
}
newResolvers.add(resolver);
}
requestMappingHandlerAdapter.setArgumentResolvers(newResolvers);
}
}4. Example controller
@RestController
@RequestMapping("/repeatbody")
public class RepeatBodyController {
@PostMapping
public Map<String, Object> create(@RequestBody User user, @RequestBody Address address) {
return Map.of("user", user, "address", address);
}
public static record User(String name, Integer age, String email) {}
public static record Address(String province, String city, String county) {}
}Running the application with Spring Boot 3.5.0 returns a JSON object containing both User and Address fields, confirming that the request body can be read multiple times without affecting other MVC functionality.
Conclusion
By replacing the default RequestResponseBodyMethodProcessor with a custom implementation that caches the request payload in a request attribute, developers can safely use multiple @RequestBody parameters in a single controller method while preserving the original request‑processing behavior.
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.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
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.
