Master Spring RestTemplate Logging with Interceptors
Learn to log Spring RestTemplate requests and responses using interceptors.
20+ years shipping production Java in banking & fintech. Lessons pulled from things that broke in production.
- ✓Basic knowledge of Spring Boot and RestTemplate
- ✓Understanding of HTTP methods and status codes
- ✓Familiarity with SLF4J logging
- Use
ClientHttpRequestInterceptorto log requests and responses. - Override
intercept()method to capture HTTP method, URI, headers, and body. - Wrap response body with
BufferingClientHttpResponseWrapperto read it multiple times. - Avoid logging sensitive data like passwords or tokens.
- Test interceptors with
MockRestServiceServeror WireMock.
Imagine you're sending a letter via courier. An interceptor is like a camera that takes a picture of the letter before it leaves and after it arrives, so you can see exactly what was sent and received. For a RestTemplate, interceptors log the HTTP request and response details for debugging or auditing.
Logging HTTP requests and responses is one of those things that seems trivial until you're debugging a production outage at 2 AM. I've been there — a fintech startup running Spring Boot 2.7 on EKS, where a third-party payment gateway started returning 400 errors, but our logs only showed "Bad Request". No request body, no headers, no response body. We were flying blind.
Spring's RestTemplate doesn't log request/response details by default. The RestTemplate class itself has no built-in logging mechanism for the actual HTTP payload. Sure, you can enable DEBUG logging for org.springframework.web.client, but that only logs URI and status code — not headers or body. That's like knowing a package was delivered but not what's inside.
Enter interceptors. By implementing ClientHttpRequestInterceptor, you can hook into the request/response lifecycle and log everything: method, URI, headers, and body. You can even wrap the response body to read it multiple times, which is crucial because by default the body stream is consumed once.
In this article, I'll show you how to build a robust logging interceptor, what the official docs don't tell you, and how to avoid the pitfalls I've seen teams fall into. By the end, you'll have a production-ready logging solution that will save you hours of debugging.
Why You Need a Custom Interceptor for Logging
Let's get one thing straight: RestTemplate's built-in logging is useless for debugging real-world issues. By default, it only logs the request URI and response status at DEBUG level. If you need to see headers or body, you're out of luck.
I've seen teams try to work around this by adding log statements before and after every restTemplate.exchange() call. That's a maintenance nightmare. You'll have dozens of duplicated log statements, and someone will inevitably forget to log the error response.
The correct approach is to use a ClientHttpRequestInterceptor. This interface lets you intercept the HTTP request before it's sent and the response after it's received. You can log everything in one place, and it automatically applies to all calls made through that RestTemplate instance.
Here's the hard truth: most teams don't discover this until they're in a production incident. Don't be that team. Add an interceptor from day one.
Let's look at the interceptor interface:
ClientHttpRequestInterceptor to centralize logging of all RestTemplate calls. Avoid scattered log statements.Building a Robust Logging Interceptor
Now let's build a production-ready interceptor that handles the stream consumption issue and adds sensible defaults. We'll use BufferingClientHttpResponseWrapper from Spring's AbstractClientHttpResponse or we can implement our own wrapper.
Spring actually provides org.springframework.http.client.BufferingClientHttpResponseWrapper but it's package-private. Instead, we can use org.springframework.web.util.ContentCachingResponseWrapper from Spring Web, but that's for servlet responses. For RestTemplate, the cleanest approach is to read the body into a byte array and then return a wrapper that serves from that array.
Here's a robust implementation:
org.springframework.http.client.BufferingClientHttpResponseWrapper but it's package-private. You can use reflection, but it's fragile. The custom wrapper above is simple and works across versions.What the Official Docs Won't Tell You
The official Spring documentation for ClientHttpRequestInterceptor is sparse. It tells you the interface exists and shows a trivial example that logs the URI. It doesn't warn you about the body stream consumption issue. It doesn't tell you that your interceptor might not be invoked if you're using AsyncRestTemplate (deprecated) or if you're using Spring Cloud Feign (which uses a different mechanism).
Here are the gotchas I've learned the hard way:
- Body stream is consumed once – I already beat this drum, but it's the number one issue. If you read the body in the interceptor, the application gets nothing. Always buffer.
- Interceptors are not inherited – If you create a new
RestTemplateinstance withnew, it doesn't have any interceptors. You must add them to every instance or use a single bean.RestTemplate() - Order matters – If you have multiple interceptors, the order they are added determines the execution order. The first interceptor added is the outermost. If you have an authentication interceptor that adds headers, your logging interceptor should be after it to see the final headers.
- Large payloads can crash your app – Logging a 10MB response body will fill your logs and memory. Always truncate or conditionally log based on size.
- Sensitive data masking – You must mask fields like
password,token,secretin both request and response bodies. Use a regex or JSON path to redact them. - Testing interceptors – You can't easily test an interceptor without an actual HTTP call. Use
MockRestServiceServerfrom Spring Test to mock the server and verify the interceptor logs correctly.
Let me show you how to handle the large payload issue:
Registering the Interceptor with RestTemplate
The builder approach is cleaner because it also lets you set timeouts, error handlers, and other settings in one place. Plus, RestTemplateBuilder is auto-configured by Spring Boot, so it inherits any global settings like MessageConverters.
If you have multiple RestTemplate beans (e.g., one for payment service, one for email service), you can add the interceptor selectively or use a composite interceptor.
@Autowired on a RestTemplate field without realizing it's a different instance. Always define a @Bean and inject that.RestTemplateBuilder to create RestTemplate beans and add interceptors. Avoid new RestTemplate() in production code.Testing Your Logging Interceptor
You can also use WireMock for integration tests. WireMock runs an actual HTTP server, which is closer to production. But for unit testing the interceptor behavior, MockRestServiceServer is sufficient.
What about verifying the log output? You can use a @SpyBean on the logger or capture log output with OutputCapture from Spring Boot test. Here's a simple way:
MockRestServiceServer and capture log output to verify logging behavior.Advanced: Conditional Logging and Masking
This interceptor logs only when the response is an error (4xx or 5xx) by default, but you can configure it to log all requests. It also masks sensitive headers and body fields.
You can make this even more configurable using properties:
The Silent 400: When Logs Lie
ClientHttpRequestInterceptor that logs the full request and response, including body, using BufferingClientHttpResponseWrapper to allow multiple reads of the response body.- Always log full request and response payloads for external calls.
- Use interceptors to centralize logging, not scattered log statements.
- Be careful with body streaming — wrap responses to avoid premature consumption.
- Mask sensitive data (passwords, tokens) in logs.
- Test your logging interceptor with actual HTTP calls using WireMock.
ClientHttpRequestInterceptor that logs request/response bodies. Use BufferingClientHttpResponseWrapper to read the response body multiple times.BufferingClientHttpResponseWrapper to cache the body in memory.RestTemplate instance used for those requests. Check if you're using a different RestTemplate bean.restTemplate.getInterceptors().add(new LoggingInterceptor());Enable DEBUG logging for your interceptor class| File | Command / Code | Purpose |
|---|---|---|
| LoggingInterceptor.java | public class LoggingInterceptor implements ClientHttpRequestInterceptor { | Why You Need a Custom Interceptor for Logging |
| LoggingInterceptor.java (truncation) | private static final int MAX_BODY_LOG_LENGTH = 1000; | What the Official Docs Won't Tell You |
| RestTemplateConfig.java (builder) | @Configuration | Registering the Interceptor with RestTemplate |
| LoggingInterceptorTest.java (log capture) | @SpringBootTest | Testing Your Logging Interceptor |
| application.yml | logging: | Advanced |
Key takeaways
ClientHttpRequestInterceptor to centralize logging of all RestTemplate calls.MockRestServiceServer to ensure it works correctly.RestTemplateBuilder for clean configuration.Interview Questions on This Topic
How would you log all HTTP requests and responses made by RestTemplate in a Spring Boot application?
ClientHttpRequestInterceptor and override intercept(). Log the request URI, method, headers, and body. For the response, use a buffered wrapper to read the body multiple times. Register the interceptor with the RestTemplate bean.Frequently Asked Questions
20+ years shipping production Java in banking & fintech. Lessons pulled from things that broke in production.
That's Spring Boot. Mark it forged?
3 min read · try the examples if you haven't