Backend Development 6 min read

How to Use Multiple @RequestBody Parameters in Spring Boot 3

This article explains why a Spring Boot controller can read the request body only once, demonstrates wrapping multiple objects in a DTO, and provides a custom HttpServletRequestWrapper with a filter to enable multiple @RequestBody parameters while noting the performance impact.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
How to Use Multiple @RequestBody Parameters in Spring Boot 3

1. Introduction

Using @RequestBody on a controller method binds the request body to a Java object. Only one request body can be read because the underlying ServletRequest#getInputStream can be called only once.

2. Solution

2.1 Wrap multiple objects in a DTO

<code>public class DTO {
  private User user;
  private Person person;
  // getter, setter
}</code>

Controller:

<code>@PostMapping("")
public Object save(@RequestBody DTO dto) {
  // TODO
  return "success";
}</code>

2.2 Wrap the HttpServletRequest

Define a custom HttpServletRequestWrapper that caches the input stream so it can be read multiple times.

<code>public class PackHttpServletRequestWrapper extends HttpServletRequestWrapper {
  private ByteArrayInputStream bais;

  public PackHttpServletRequestWrapper(HttpServletRequest request) {
    super(request);
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    try {
      StreamUtils.copy(request.getInputStream(), baos);
      bais = new ByteArrayInputStream(baos.toByteArray());
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  @Override
  public ServletInputStream getInputStream() throws IOException {
    bais.reset();
    return new ServletInputStream() {
      @Override public boolean isFinished() { return false; }
      @Override public boolean isReady() { return false; }
      @Override public void setReadListener(ReadListener listener) {}
      @Override public int read() throws IOException { return bais.read(); }
    };
  }
}</code>

Filter that replaces the request with the wrapper:

<code>public class PackRequestWrapperFilter implements Filter {
  @Override
  public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
      throws IOException, ServletException {
    HttpServletRequest request = (HttpServletRequest) req;
    PackHttpServletRequestWrapper requestWrapper = new PackHttpServletRequestWrapper(request);
    chain.doFilter(requestWrapper, resp);
  }
}</code>

Register the filter in Spring Boot:

<code>@Bean
FilterRegistrationBean<Filter> packRequestWrapperFilter() {
  FilterRegistrationBean<Filter> reg = new FilterRegistrationBean<>();
  reg.setFilter(new PackRequestWrapperFilter());
  reg.setUrlPatterns(List.of("/*"));
  return reg;
}</code>

The above configuration allows multiple @RequestBody parameters by reading the cached stream each time.

Note: Using multiple @RequestBody parameters incurs performance overhead because the request body is read and converted multiple times.

JavaSpring BootfilterRequestBodyHttpServletRequestWrapper
Spring Full-Stack Practical Cases
Written by

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.

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.