Filter is part of the Servlet API. In a Spring Boot web app, filters sit in the Servlet container chain and can intercept every HTTP request/response before it reaches your controller (and after it leaves)
@RestController) processes it ->UserApiController@Slf4j
@RestController
@RequestMapping("/api/user")
public class UserApiController {
@PostMapping("")
public void register(
@RequestBody
UserRequest userRequest
){
log.info("{}", userRequest);
}
}
UserRequest@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@JsonNaming(value = PropertyNamingStrategy.SnakeCaseStrategy.class)
public class UserRequest {
private String name;
private String phoneNumber;
private String email;
private Integer age;
}
{
"user_name": "Choi Seunghwan",
"phoneNumber": "020-2231-4213",
"age": 100,
"email": "choi@gmail.com"
}
@Valid or any other validation method, a status code 200 will be returned but object mapping will not be done properly. Below userRequest is logged (We only see the parsed UserRequest object):UserRequest(name=null, phoneNumber=null, email=choi@gmail.com, age=100)
The filter sees the raw HTTP request before Spring parses it - headers, method, URI, and raw JSON body, which can be useful for debugging malformed requests, auditing exact input payloads, detecting suspicious/malicious traffic.
LoggingFilter
@Slf4j
@Component
public class LoggerFilter implements Filter{
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//before entry
log.info("before entry");
var req = new ContentCachingRequestWrapper((HttpServletRequest) request);
var res = new ContentCachingResponseWrapper((HttpServletResponse) response);
chain.doFilter(req, res);
var reqJSON = new String(req.getContentAsByteArray());
log.info("req : {}", reqJSON);
var resJSON = new String(res.getContentAsByteArray());
log.info("res : {}", resJSON);
log.info("after entry");
//after entry
res.copyBodyToResponse();
}
}
var req = new ContentCachingRequestWrapper((ContentCachingRequestWrapper) request);
var res = new ContentCachingResponseWrapper((ContentCachingResponseWrapper) response);
ContentCachingRequestWrapper caches the request body so it can be read after Spring consumes it for JSON deserialization.ContentCachingResponseWrapper caches the response body so it can be logged after the controller executes.HTTPServletRequestWrapper:HTTPServletRequestWrapper (and HttpServletResponseWrapper) do not cache the body by default.getReader() or getInputStream().getReader() in the filter on a plain HTTPServletRequestWrapper, Spring won't be able to read it later, leading to IllegalStateException: getReader() has already been called for this request.ContentCachingRequestWrapper buffers the request body in memory. getReader() in our filter consumes the stream.2025-08-17T20:25:57.257+08:00 INFO 66375 --- [filter] [nio-8080-exec-1] com.example.filter.filter.LoggerFilter : req : {
"user_name": "Choi Seunghwan",
"phoneNumber": "020-2231-4213",
"age": 100,
"email": "choi@gmail.com"
}
2025-08-17T20:25:57.258+08:00 INFO 66375 --- [filter] [nio-8080-exec-1] com.example.filter.filter.LoggerFilter : res : {"name":null,"phone_number":null,"email":"choi@gmail.com","age":100}
2025-08-17T20:25:57.259+08:00 INFO 66375 --- [filter] [nio-8080-exec-1] com.example.filter.filter.LoggerFilter : after entry