RestTemplate - API Communication
👉 Master synchronous HTTP calls, timeouts, interceptors, error handling and best practices in Spring Boot.
What is Spring’s RestTemplate ?
Spring RestTemplate is a synchronous HTTP client used to perform RESTful requests and simplify communication between microservices.
It abstracts the complexity of low-level HTTP client libraries by providing a template-method API that handles request creation, execution, and response mapping to Java objects.
In a microservice architecture, one service often needs to fetch data from another. RestTemplate acts as the bridge, allowing the “Client” service to consume resources from a “Provider” service over HTTP.
Why use RestTemplate ?
RestTemplate exists to standardize how Spring applications communicate with external REST APIs. It handles boilerplate tasks like opening connections, managing headers, and converting JSON/XML payloads into POJOs.
- Simplified API:
- Offers high-level methods like
getForEntity,postForObject, andexchange.
- Offers high-level methods like
- Automatic Serialization:
- Integration with Jackson ensures JSON responses are mapped to Java beans automatically.
- Synchronous Execution:
- The calling thread blocks until the response is received, ensuring a straightforward sequential flow.
- Customizable:
- Supports interceptors, error handlers, and message converters for production-grade requirements.
Application configuration
To use RestTemplate in Spring Cloud environment, application must have the Spring Web starter dependency.
This dependency provides the necessary infrastructure for both hosting as well as consuming RESTful services.
Maven dependency
Ensure below dependency is present in application’s pom.xml, add if missing.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
RestTemplate configuration
In production environments, using the default RestTemplate constructor is discouraged because it lacks timeout controls.
Instead, use a RestTemplateBuilder to configure a shared, thread-safe Bean, autowired by spring in service class.
Define a configuration class to manage the lifecycle and settings of the client.
@Configuration
public class RestTemplateConfiguration {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.setConnectTimeout(Duration.ofMillis(3000)) // Time to establish connection
.setReadTimeout(Duration.ofMillis(5000)) // Time to wait for data
.build();
}
}
Consume Rest services
Communicating between services requires a Response Bean to hold the data and a Client to initiate the request.
Below example demonstrates how a to build RestTemplate and consume Rest services.
1. The Data Model (POJO)
The response bean must have fields that match the JSON structure returned by the provider service.
public class CurrencyConversion {
private Long id;
private String from;
private String to;
private BigDecimal conversionMultiples;
private BigDecimal quantity;
private BigDecimal totalCalculatedAmount;
private String environment;
// Default constructors, getters, and setters
}
2. The service API integration
Inject the RestTemplate bean and use getForEntity to perform a GET request with URI variables.
@RestController
public class CurrencyConversionController {
// Update this to your app base url.
private static final String BASE_URL = "http://localhost:8000";
private static final String EXCHANGE_API_PATH =
"/currency-exchange/from/{from}/to/{to}";
@Autowired
private RestTemplate restTemplate;
@GetMapping("/currency-conversion/from/{from}/to/{to}/q/{quantity}")
public CurrencyConversion calculateConversion(
@PathVariable String from,
@PathVariable String to,
@PathVariable BigDecimal quantity) {
// 1. Prepare URI Variables
Map<String, String> uriVariables = new HashMap<>();
uriVariables.put("from", from.toUpperCase());
uriVariables.put("to", to.toUpperCase());
// 2. Execute Request
ResponseEntity<CurrencyConversion> response =
restTemplate.getForEntity(
BASE_URL + EXCHANGE_API_PATH,
CurrencyConversion.class,
uriVariables);
// 3. Null Safety Check (Crucial for Beginners)
CurrencyConversion result = response.getBody();
if (result == null) {
throw new RuntimeException(
"Data not found for the requested currency pair.");
}
// 4. Perform Calculation
result.setQuantity(quantity);
BigDecimal multiple = result.getConversionMultiples();
if (multiple != null) {
result.setTotalCalculatedAmount(
quantity.multiply(multiple));
}
return result;
}
}
RestTemplate lifecycle
The RestTemplate Request-Response Lifecycle describes the internal flow of an HTTP call, starting from the application code through the underlying HTTP client to the external service.
This sequence ensures that every request is intercepted, executed, and converted back into a usable Java format.
sequenceDiagram
participant App as Client Application
participant RT as RestTemplate
participant Int as Interceptors
participant Net as External Microservice
participant Conv as MessageConverter
App->>RT: call getForEntity() / exchange()
RT->>Int: processInterceptors (Auth, Logging)
Int->>Net: Send HTTP Request
Note right of Net: Provider processes logic
Net-->>Int: Return HTTP Response
Int-->>RT: Pass Response
RT->>RT: execute ErrorHandler
RT->>Conv: convert Response Body to POJO
Conv-->>RT: Java Object
RT-->>App: ResponseEntity<T>
Core components in the lifecycle
Several key components collaborate during this lifecycle to ensure reliable communication between the microservices. Each component has a specific responsibility in the transformation of data.
| Component | Purpose |
|---|---|
| ClientHttpRequestFactory | The engine that creates the actual HTTP connection using libraries like Apache HttpClient or OkHttp. |
| HttpMessageConverters | Responsible for marshaling and unmarshaling the data, such as converting a Java object to JSON for a POST request. |
| ResponseErrorHandler | A strategy interface used to determine whether a particular HTTP response has an error and how to handle it. |
| ClientHttpRequestInterceptor | A mechanism to intercept outgoing requests to add headers, such as Bearer Tokens or Correlation IDs for distributed tracing. |
In-Built default strategies
The table below summarizes how RestTemplate manages different parts of the HTTP lifecycle to maintain flexibility.
| Component | Responsibility | Default Implementation |
|---|---|---|
| Request Factory | Connection Management | SimpleClientHttpRequestFactory |
| Converters | Data Transformation | MappingJackson2HttpMessageConverter |
| Error Handling | Status Code Validation | DefaultResponseErrorHandler |
Client request interceptors
A ClientHttpRequestInterceptor is a functional interface in Spring that allows you to intercept outgoing HTTP requests and incoming responses.
It acts as a middleware for RestTemplate, enabling developers to globally modify headers, inject authentication tokens, or log request/response metadata across all microservice interactions.
In a distributed system, interceptors are critical for maintaining observability. Without them, debugging failures between services becomes difficult, as there is no central record of what was sent or received.
Why to use interceptors ?
Interceptors solve the problem of repetitive code by centralizing cross-cutting concerns for HTTP communication.
- Distributed Tracing:
- Injecting
X-B3-TraceIdorcorrelation IDsto track a request across multiple services.
- Injecting
- Security:
- Automatically attaching
JWTorBearer tokensto every outgoing call.
- Automatically attaching
- Audit Logging:
- Recording the URL, method, and execution time of every external request for compliance.
- Performance Monitoring:
- Measuring how long downstream services take to respond.
Implementation: Logging Interceptor
To log the request and response body, we have to implement the ClientHttpRequestInterceptor interface.
Note: To log the response body without consuming the stream (which would prevent the application from reading it), we must use a specific ClientHttpRequestFactory.
1. Implement the interceptor
This class captures the request details before execution and logs the response status after the call returns.
public class RestTemplateLoggingInterceptor
implements ClientHttpRequestInterceptor {
private static final Logger log = LoggerFactory
.getLogger(RestTemplateLoggingInterceptor.class);
@Override
public ClientResponse intercept(HttpRequest request,
byte[] body,
ClientHttpRequestExecution execution)
throws IOException {
logRequest(request, body);
ClientHttpResponse response = execution.execute(request, body);
logResponse(response);
return response;
}
private void logRequest(HttpRequest request, byte[] body) {
log.info("Request: {} {}", request.getMethod(), request.getURI());
log.debug("Request Body: {}",
new String(body, StandardCharsets.UTF_8));
}
private void logResponse(ClientHttpResponse response)
throws IOException {
log.info("Response Status: {}", response.getStatusCode());
}
}
2. Register the interceptor
When defining your RestTemplate bean, we must register the interceptor and wrap the factory in a BufferingClientHttpRequestFactory. So, the response stream can be read twice (once for logging, another by the service).
@Configuration
public class RestTemplateConfiguration {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
RestTemplate restTemplate = builder
.setConnectTimeout(Duration.ofMillis(3000))
.setReadTimeout(Duration.ofMillis(5000))
.build();
// Enables buffering, so response body can be read multiple times
restTemplate.setRequestFactory(
new BufferingClientHttpRequestFactory(
new SimpleClientHttpRequestFactory()));
List<ClientHttpRequestInterceptor> interceptors =
restTemplate.getInterceptors();
if (CollectionUtils.isEmpty(interceptors)) {
interceptors = new ArrayList<>();
}
interceptors.add(new RestTemplateLoggingInterceptor());
restTemplate.setInterceptors(interceptors);
return restTemplate;
}
}
Caution:
→ Use theBufferingClientHttpRequestFactorywith caution, If the downstream service returns a large payload (e.g., a 10MB JSON), it will load the entire payload into memory, which can lead toOutOfMemoryErrorunder high load.
Benefits of interceptor strategy
Using interceptors ensures your microservice architecture remains clean and maintainable.
| Feature | Without Interceptors | With Interceptors |
|---|---|---|
| Code Duplication | High (Log in every controller) | Zero (Centralized) |
| Maintenance | Difficult (Update every call) | Easy (Update one class) |
| Traceability | Manual / Fragmented | Automatic / Consistent |
Handling communication failures
In production-grade microservices, Communication Failures occur when a downstream service is unavailable, slow, or returns error status codes.
RestTemplate manages these failures through a specialized ResponseErrorHandler interface, which prevents the application from crashing due to unhandled 4xx or 5xx exceptions.
Handling these failures correctly is vital for system resilience. If a “Client” service does not handle a “Provider” service failure gracefully, it can lead to a cascading failure that brings down the entire distributed system.
1. Custom ResponseErrorHandler
The ResponseErrorHandler is a strategy interface used to determine if a response has an error and how to process it before it reaches your business logic.
In production grade apps, this is used to map obscure HTTP errors into meaningful domain-specific exceptions.
RestTemplateErrorHandler impl
Below error handler, identifies the error responses and do the needful to process the error gracefully.
@Component
public class CustomRestTemplateErrorHandler
implements ResponseErrorHandler {
@Override
public boolean hasError(ClientHttpResponse response)
throws IOException {
return response.getStatusCode().isError();
}
@Override
public void handleError(ClientHttpResponse response)
throws IOException {
if (response.getStatusCode().is4xxClientError()) {
// Logic for 4xx: Log and throw custom ClientException
throw new ResourceNotFoundException(
"Downstream service resource not found.");
} else if (response.getStatusCode().is5xxServerError()) {
// Logic for 5xx: Log and throw custom ServerException
throw new ServiceUnavailableException(
"Downstream service is currently unavailable.");
}
}
}
2. Production resilience patterns
While a ResponseErrorHandler handles the logic of an error, production-grade apps use Resilience Patterns to manage the behavior of the connection itself.
We will be discuss in details on these topics in upcoming sessions.
Insight:
→ Relying solely on error handling is insufficient for high-availability systems.
Key resilience strategies
- Circuit Breaker:
- Prevents the application from repeatedly calling a failing service, allowing it time to recover.
- Retry Pattern:
- Automatically re-attempts a failed request a specific number of times (useful for transient network glitches).
- Bulkhead:
- Limits the number of concurrent calls to a specific service to prevent resource exhaustion.
- Timeouts:
- Ensures a slow service does not hang the calling thread indefinitely (as discussed in configuration).
3. Error handling strategies
Choosing the right strategy depends on the criticality of the downstream data.
| Strategy | Best For | Implementation |
|---|---|---|
| ResponseErrorHandler | Mapping HTTP status to Exceptions | Custom Java Class |
| Try-Catch Blocks | One-off, specific logic calls | Inline Controller code |
| Resilience4j | Preventing cascading failures | @CircuitBreaker Annotation |
| Fallback Methods | Providing default data when service fails | Secondary “Default” method |
4. Best Practices
To ensure that the microservice remains robust, follow these architectural principles for communication.
- Never Use Defaults:
- Always configure explicit connect and read timeouts.
- Log Metadata, Not Data:
- Log the request URL and status code.
- Avoid logging sensitive payload data.
- Use Fallbacks:
- If a service fails return either of below instead of an error page.
- A cached value
- A meaningful default response
- If a service fails return either of below instead of an error page.
- Propagate Traces:
- Ensure your interceptors pass correlation IDs to track the error across service boundaries.
Other communication clients
Spring provides multiple ways to handle service-to-service communication depending on your project requirements.
| Feature | Style | Boilerplate | Status |
|---|---|---|---|
| RestTemplate | Synchronous, Template-based | High (needs URL, Map, etc.) | Maintenance Mode |
| RestClient(Spring 6.1+) | Synchronous, Fluent API | Medium (Chainable methods) | Active / Recommended |
| OpenFeign | Declarative, Interface-based | Very Low (Annotations only) | Active (Spring Cloud) |
Architect's Note:
→ While RestTemplate is the industry standard for older projects.
→ OpenFeign is preferred in Spring Cloud for its cleaner, interface-driven approach.
References:
→ Spring documentation on RestTemplate.