spring boot
request body
modify request
mvc interceptor
java development

How to modify request body before reaching controller in spring boot

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Spring Boot controllers usually read request bodies once, so body transformation must happen before the controller consumes input. This is common for tasks like payload normalization, encrypted body decoding, schema adaptation, and tenant metadata injection. The safest technique is a filter plus request wrapper that replaces the readable body stream.

Core Sections

Why a wrapper is required

HttpServletRequest input streams are one time read by default. If middleware reads the body without wrapping, controllers may see empty content. A custom HttpServletRequestWrapper lets you store modified bytes and expose them through getInputStream and getReader.

java
1import jakarta.servlet.ReadListener;
2import jakarta.servlet.ServletInputStream;
3import jakarta.servlet.http.HttpServletRequest;
4import jakarta.servlet.http.HttpServletRequestWrapper;
5
6import java.io.*;
7import java.nio.charset.StandardCharsets;
8
9public class CachedBodyRequestWrapper extends HttpServletRequestWrapper {
10    private final byte[] body;
11
12    public CachedBodyRequestWrapper(HttpServletRequest request, byte[] body) {
13        super(request);
14        this.body = body;
15    }
16
17    @Override
18    public ServletInputStream getInputStream() {
19        ByteArrayInputStream bais = new ByteArrayInputStream(body);
20        return new ServletInputStream() {
21            @Override
22            public int read() {
23                return bais.read();
24            }
25
26            @Override
27            public boolean isFinished() {
28                return bais.available() == 0;
29            }
30
31            @Override
32            public boolean isReady() {
33                return true;
34            }
35
36            @Override
37            public void setReadListener(ReadListener readListener) {
38                // not used for this synchronous wrapper
39            }
40        };
41    }
42
43    @Override
44    public BufferedReader getReader() {
45        return new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8));
46    }
47}

Build a filter that modifies JSON body

Use OncePerRequestFilter so execution happens once per request dispatch. Read original bytes, transform, then forward wrapped request.

java
1import com.fasterxml.jackson.databind.ObjectMapper;
2import jakarta.servlet.FilterChain;
3import jakarta.servlet.ServletException;
4import jakarta.servlet.http.HttpServletRequest;
5import jakarta.servlet.http.HttpServletResponse;
6import org.springframework.web.filter.OncePerRequestFilter;
7
8import java.io.IOException;
9import java.nio.charset.StandardCharsets;
10import java.util.Map;
11
12public class BodyTransformFilter extends OncePerRequestFilter {
13    private final ObjectMapper mapper = new ObjectMapper();
14
15    @Override
16    protected void doFilterInternal(HttpServletRequest request,
17                                    HttpServletResponse response,
18                                    FilterChain filterChain) throws ServletException, IOException {
19
20        if (!"application/json".equalsIgnoreCase(request.getContentType())) {
21            filterChain.doFilter(request, response);
22            return;
23        }
24
25        byte[] original = request.getInputStream().readAllBytes();
26        Map<String, Object> payload = mapper.readValue(original, Map.class);
27        payload.put("source", "gateway");
28
29        byte[] modified = mapper.writeValueAsString(payload).getBytes(StandardCharsets.UTF_8);
30        CachedBodyRequestWrapper wrapped = new CachedBodyRequestWrapper(request, modified);
31        filterChain.doFilter(wrapped, response);
32    }
33}

Register the filter with a FilterRegistrationBean when ordering matters relative to security and logging filters.

Alternative with RequestBodyAdvice

If you only need to transform objects after deserialization and before controller method execution, RequestBodyAdvice can be cleaner than raw byte manipulation.

java
1import org.springframework.core.MethodParameter;
2import org.springframework.http.HttpInputMessage;
3import org.springframework.http.converter.HttpMessageConverter;
4import org.springframework.web.bind.annotation.ControllerAdvice;
5import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;
6
7@ControllerAdvice
8public class PayloadAdvice extends RequestBodyAdviceAdapter {
9    @Override
10    public boolean supports(MethodParameter methodParameter,
11                            java.lang.reflect.Type targetType,
12                            Class<? extends HttpMessageConverter<?>> converterType) {
13        return true;
14    }
15
16    @Override
17    public Object afterBodyRead(Object body,
18                                HttpInputMessage inputMessage,
19                                MethodParameter parameter,
20                                java.lang.reflect.Type targetType,
21                                Class<? extends HttpMessageConverter<?>> converterType) {
22        return body;
23    }
24}

Use filter plus wrapper for raw transport level changes. Use body advice for object level policy changes.

Common Pitfalls

  • Reading request body in a filter without wrapping and forwarding modified bytes. Controllers then receive empty body.
  • Transforming every content type as JSON without checks. Gate logic by content type and path.
  • Ignoring filter order relative to security and tracing filters. Set explicit order in registration.
  • Performing expensive parsing in global filters unnecessarily. Narrow scope to matching endpoints.
  • Mixing transport and domain transformations in one class. Separate byte level and object level concerns.

Summary

  • Request body modification must happen before controller consumption.
  • A custom HttpServletRequestWrapper is the core technique for replacing request body bytes.
  • OncePerRequestFilter provides predictable interception points.
  • RequestBodyAdvice is useful for post deserialization object transformation.
  • Clear layering and filter ordering prevent subtle production bugs.

Additional implementation notes: verify behavior with realistic tests, document assumptions, and keep boundary handling explicit so long term maintenance stays predictable.


Course illustration
Course illustration

All Rights Reserved.