When implementing a custom interceptor in a Spring Boot application to validate specific parameters, an exception Required request body is missing may occur. This issue typically arises after adding an interceptor that reads the request body, causing subsequent attempts to read the body (such as via @RequestBody) to fail.
The root cause is that the HttpServletRequest input stream (getInputStream()) can only be read once. If the interceptor consumes the stream to inspect the payload, the dispatcher servlet cannot read it again when processing the controller method.
To solve this, wrap the request in a custom wrapper that caches the body content. Use a Filter to replace the incoming request with this wrapper, allowing the stream to be read multiple times.
Custom Request Wrapper
This class caches the request body bytes in a ByteArrayInputStream.
import org.springframework.util.StreamUtils;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.StandardCharsets;
public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
private final byte[] cachedPayload;
public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
super(request);
// Cache the original body bytes
this.cachedPayload = StreamUtils.copyToByteArray(request.getInputStream());
}
@Override
public ServletInputStream getInputStream() {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedPayload);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return byteArrayInputStream.available() == 0;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readListener) {
// Not implemented for simplicity
}
@Override
public int read() {
return byteArrayInputStream.read();
}
};
}
@Override
public BufferedReader getReader() {
return new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8));
}
public String getCachedBodyAsString() {
return new String(this.cachedPayload, StandardCharsets.UTF_8);
}
}
Filter Configuraton
Register a filter to wrap every incoming request with the custom wrapper.
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@Component
@WebFilter(urlPatterns = "/**")
public class RequestCachingFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
if (servletRequest instanceof HttpServletRequest) {
CachedBodyHttpServletRequest wrappedRequest =
new CachedBodyHttpServletRequest((HttpServletRequest) servletRequest);
filterChain.doFilter(wrappedRequest, servletResponse);
} else {
filterChain.doFilter(servletRequest, servletResponse);
}
}
}
Accessing Body in Interceptor
Now, the interceptor can read the body without breaking the controller's @RequestBody.
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class AuthInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(AuthInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
String payload = "";
if (request instanceof CachedBodyHttpServletRequest) {
payload = ((CachedBodyHttpServletRequest) request).getCachedBodyAsString();
} else {
// Fallback or log error
return false;
}
// Example: Extract a token from the JSON payload
try {
JSONObject json = JSONObject.parseObject(payload);
String authToken = json.getString("sessionToken");
logger.info("Extracted Token: {}", authToken);
// Perform validation logic here...
return true;
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
return false;
}
}
}