Request Customization
About
RestTemplate
provides several hooks and constructs to allow request customization a critical aspect when interacting with external APIs or internal microservices that require authentication, custom headers, request transformations, logging, tracing, or advanced configuration like timeouts and interceptors.
Customizing a request ensures that our API calls are:
Secure (e.g., with bearer tokens or API keys),
Compliant (e.g., with required content-type or correlation IDs),
Reliable (e.g., with retries, timeouts, and fallbacks),
Traceable (e.g., with custom headers for distributed tracing).
Common Use Cases
Use Case
Customization Example
Add Path and Query Param
Set Query, Path parameters
Add Request Body
Set API Request body
Add authentication headers
Bearer token, API key, Basic Auth
Include trace/correlation IDs
Pass unique request ID for observability
Change content type / accept type
application/json
, application/xml
, custom media types
Modify request payload
Pre-serialize object, or add additional request fields
Set timeouts or connection pool
Customize via underlying HttpClient
or RestTemplateBuilder
Pre-process request before sending
Interceptors, ClientHttpRequestInterceptor
Add dynamic headers from context
Extract values from MDC, ThreadLocal, or SecurityContext
Add Path and Query Param
When interacting with external or internal REST APIs, it is common to dynamically construct the request URL by appending:
Path Parameters — values embedded directly within the URL path (e.g.,
/user/{id}
),Query Parameters — key-value pairs appended after the
?
in the URL (e.g.,?status=active&limit=10
).
Spring’s RestTemplate
provides multiple ways to inject these parameters cleanly and maintainably.
1. Using Path Variables with URI Template Expansion
This is the most common and safe way to replace path variables in endpoint templates using placeholders.
String url = "https://api.example.com/users/{userId}/orders/{orderId}";
Map<String, String> uriVariables = Map.of(
"userId", "12345",
"orderId", "987"
);
ResponseEntity<OrderResponse> response = restTemplate.getForEntity(
url,
OrderResponse.class,
uriVariables
);
Path placeholders (
{userId}
,{orderId}
) are automatically replaced by values from theMap
.No need for string concatenation.
Prevents URL injection or malformed paths.
2. Path Params via UriComponentsBuilder
for Dynamic URIs
UriComponentsBuilder
for Dynamic URIsUse UriComponentsBuilder
when we want to construct complex URLs that include both path variables and query parameters dynamically.
URI uri = UriComponentsBuilder
.fromUriString("https://api.example.com/users/{userId}/orders")
.queryParam("page", 1)
.queryParam("limit", 50)
.queryParam("sortBy", "date")
.buildAndExpand("12345")
.toUri();
ResponseEntity<OrderList> response = restTemplate.getForEntity(uri, OrderList.class);
buildAndExpand("12345")
replaces{userId}
.queryParam()
is used for clean and safe appending of query parameters.Works great for pagination, filtering, and sorting APIs.
3. Fully Programmatic Path + Query Construction
This is helpful when query params are optional or dynamically built based on logic:
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add("category", "books");
queryParams.add("sort", "title");
queryParams.add("sort", "price"); // multiple values for same key
URI uri = UriComponentsBuilder
.fromUriString("https://api.example.com/users/{userId}/cart")
.queryParams(queryParams)
.buildAndExpand("u7890")
.encode()
.toUri();
ResponseEntity<CartResponse> response = restTemplate.exchange(
uri,
HttpMethod.GET,
null,
CartResponse.class
);
MultiValueMap
handles multiple query parameters with the same key.encode()
ensures proper URI encoding for special characters.
4. Mixing Path and Query Params in POST Request
Even in POST calls (or PUT), query parameters are often used for metadata like versioning or flags.
URI uri = UriComponentsBuilder
.fromUriString("https://api.example.com/users/{id}/upload")
.queryParam("async", "true")
.queryParam("version", "v2")
.buildAndExpand("user123")
.toUri();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<FileUploadRequest> request = new HttpEntity<>(fileRequest, headers);
ResponseEntity<FileUploadResponse> response = restTemplate.exchange(
uri,
HttpMethod.POST,
request,
FileUploadResponse.class
);
Add Request Body
When making HTTP methods like POST
, PUT
, or PATCH
, the client often needs to send a structured request body typically in JSON or XML format. This body carries the actual business payload (e.g., user details, payment data, file metadata).
Spring’s RestTemplate
allows for seamless serialization of Java objects into the body of a request using its built-in message converters (usually MappingJackson2HttpMessageConverter
for JSON).
HttpEntity<T>
is used to encapsulate:Body – the Java object (payload).
Headers – content type, authentication, etc.
Always specify
Content-Type
(application/json
) to avoid serialization issues.
1. POST Request with JSON Body
UserCreateRequest user = new UserCreateRequest("john.doe", "password123");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<UserCreateRequest> requestEntity = new HttpEntity<>(user, headers);
ResponseEntity<UserResponse> response = restTemplate.postForEntity(
"https://api.example.com/users",
requestEntity,
UserResponse.class
);
user
is automatically serialized into JSON.The content-type is declared explicitly.
Response is mapped to
UserResponse
.
2. PUT Request with Request Body
UserUpdateRequest updateRequest = new UserUpdateRequest("john.doe", "[email protected]");
HttpEntity<UserUpdateRequest> entity = new HttpEntity<>(updateRequest);
restTemplate.put("https://api.example.com/users/{id}", entity, "123");
PUT operations use
restTemplate.put()
.Path variable (
{id}
) is passed separately.
3. Exchange Method for Full Control (POST, PUT, PATCH)
For advanced use cases like:
Passing custom headers
Supporting non-JSON content
Handling dynamic URLs
Use exchange()
method:
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("X-Correlation-Id", "txn-456789");
PaymentRequest payload = new PaymentRequest("user123", 100.0);
HttpEntity<PaymentRequest> request = new HttpEntity<>(payload, headers);
URI uri = UriComponentsBuilder
.fromUriString("https://api.example.com/payments?async=true")
.build()
.toUri();
ResponseEntity<PaymentResponse> response = restTemplate.exchange(
uri,
HttpMethod.POST,
request,
PaymentResponse.class
);
4. Sending a List or Collection in Request Body
For APIs that accept arrays or lists:
List<String> userIds = List.of("u123", "u124", "u125");
HttpEntity<List<String>> request = new HttpEntity<>(userIds);
ResponseEntity<GroupResponse> response = restTemplate.postForEntity(
"https://api.example.com/groups/{groupId}/users",
request,
GroupResponse.class,
"teamA"
);
5. Sending a Form-Encoded Body (Non-JSON)
If the backend expects a URL-encoded form body:
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("username", "john");
map.add("password", "secret");
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
ResponseEntity<TokenResponse> response = restTemplate.postForEntity(
"https://auth.example.com/oauth/token",
request,
TokenResponse.class
);
Add authentication headers
In most enterprise applications, outbound HTTP requests need to include authentication details—whether via Bearer tokens, API keys, Basic Auth, or custom headers—to access protected APIs.
Spring’s RestTemplate
allows injecting headers easily via HttpHeaders
, which are then bundled with the request body using HttpEntity
.
Headers are included using
HttpHeaders
, which supports all standard and custom header types.Auth headers are most often added as:
Authorization: Bearer <token>
(OAuth/JWT)Authorization: Basic <base64>
(Basic Auth)X-API-Key: <key>
(Custom API key)
HttpEntity
is used to combine headers and optional body for request.
1. Bearer Token Authentication
Used widely with OAuth 2.0, JWT, and internal token-based auth:
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...");
HttpEntity<Void> entity = new HttpEntity<>(headers);
ResponseEntity<AccountResponse> response = restTemplate.exchange(
"https://api.example.com/accounts/{id}",
HttpMethod.GET,
entity,
AccountResponse.class,
"acc123"
);
setBearerAuth()
automatically adds the header:Authorization: Bearer <token>
2. Basic Authentication
For services still using basic HTTP authentication:
String plainCreds = "username:password";
String base64Creds = Base64.getEncoder().encodeToString(plainCreds.getBytes());
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Basic " + base64Creds);
HttpEntity<Void> entity = new HttpEntity<>(headers);
ResponseEntity<EmployeeData> response = restTemplate.exchange(
"https://internal.example.com/employees",
HttpMethod.GET,
entity,
EmployeeData.class
);
Encodes the string as
Authorization: Basic <encoded-credentials>
3. API Key as Header
Some services use API keys passed in custom headers like X-API-Key
or x-client-secret
:
HttpHeaders headers = new HttpHeaders();
headers.add("X-API-Key", "api-key-987654321");
HttpEntity<Void> request = new HttpEntity<>(headers);
ResponseEntity<InvoiceResponse> response = restTemplate.exchange(
"https://thirdparty.com/invoices",
HttpMethod.GET,
request,
InvoiceResponse.class
);
4. With Request Body and Auth Headers (POST)
When sending both payload and headers:
PaymentRequest payment = new PaymentRequest("txn789", 150.00);
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth("secure-token-123");
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<PaymentRequest> requestEntity = new HttpEntity<>(payment, headers);
ResponseEntity<PaymentResponse> response = restTemplate.postForEntity(
"https://api.example.com/payments",
requestEntity,
PaymentResponse.class
);
5. Dynamically Inject Headers via Interceptor (Reusable)
If every request should carry a token:
public class AuthInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
request.getHeaders().setBearerAuth("dynamic-token-here");
return execution.execute(request, body);
}
}
Register it:
RestTemplate restTemplate = new RestTemplate();
restTemplate.getInterceptors().add(new AuthInterceptor());
Recommended when a common token applies to all requests.
Encouraged over repeating header logic in every request.
Include trace/correlation IDs
In distributed systems and microservices, correlation IDs (also called trace IDs or request IDs) are essential for request tracking, observability, debugging, and log correlation across service boundaries.
When making HTTP calls using RestTemplate
, it's a common practice to propagate the correlation ID received in the incoming request to any downstream services.
1. Manually Add a Correlation ID to Headers
If the correlation ID is generated earlier (e.g., in a filter or controller), inject it into outgoing request headers.
String correlationId = MDC.get("X-Correlation-ID"); // From thread-local logging context
HttpHeaders headers = new HttpHeaders();
headers.add("X-Correlation-ID", correlationId);
HttpEntity<Void> entity = new HttpEntity<>(headers);
ResponseEntity<CustomerResponse> response = restTemplate.exchange(
"https://api.partner.com/customers/{id}",
HttpMethod.GET,
entity,
CustomerResponse.class,
"cust-101"
);
Header Name: Use a consistent name like
X-Correlation-ID
ortraceId
.
2. Automatically Inject Correlation ID via Interceptor
To avoid repeating this logic everywhere, use a ClientHttpRequestInterceptor
:
public class CorrelationIdInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(
HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
String correlationId = MDC.get("X-Correlation-ID");
if (correlationId != null) {
request.getHeaders().add("X-Correlation-ID", correlationId);
}
return execution.execute(request, body);
}
}
Register the interceptor:
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getInterceptors().add(new CorrelationIdInterceptor());
return restTemplate;
}
Now every outbound call via
RestTemplate
will carry the correlation ID.
3. Integration with Spring Cloud Sleuth
If we use Spring Cloud Sleuth, it automatically manages trace IDs and span IDs:
X-B3-TraceId: 7b63a1d95e0e33c7
X-B3-SpanId: d7a1fbea4e3fa0b7
X-B3-Sampled: 1
We can keep this in sync with our custom X-Correlation-ID
by configuring MDC.
4. Handling Missing Correlation IDs
In a gateway or edge service, it’s common to generate a correlation ID if not already present:
String correlationId = Optional.ofNullable(MDC.get("X-Correlation-ID"))
.orElse(UUID.randomUUID().toString());
MDC.put("X-Correlation-ID", correlationId);
Then pass this into headers downstream.
Change content type / accept type
When consuming external or internal REST APIs, it's crucial to explicitly set Content-Type
and Accept
headers to align with what the server expects and returns. These headers define:
Content-Type
: The format of the request body being sent.Accept
: The format that the client can process in the response.
Failing to set these correctly can lead to:
415 Unsupported Media Type
406 Not Acceptable
Unexpected deserialization failures
Typical Scenarios
Sending JSON payload
application/json
application/json
Consuming XML-based third-party API
application/xml
application/xml
Posting form data
application/x-www-form-urlencoded
Depends on API, often JSON or text
Multipart file upload
multipart/form-data
Usually JSON or plain text
Consuming text or CSV file
text/plain
, text/csv
text/plain
, text/csv
1. Set Content-Type and Accept-Type Explicitly
We can customize both headers using HttpHeaders
:
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
MyRequest requestPayload = new MyRequest("sample", 42);
HttpEntity<MyRequest> requestEntity = new HttpEntity<>(requestPayload, headers);
ResponseEntity<MyResponse> response = restTemplate.exchange(
"https://partner.api.com/process",
HttpMethod.POST,
requestEntity,
MyResponse.class
);
2. Changing Content-Type for Form Submission
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
form.add("username", "john.doe");
form.add("password", "securePass");
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(form, headers);
ResponseEntity<String> response = restTemplate.postForEntity(
"https://auth.example.com/login",
entity,
String.class
);
3. Accepting XML Response from External Service
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_XML));
HttpEntity<Void> entity = new HttpEntity<>(headers);
ResponseEntity<MyXmlResponse> response = restTemplate.exchange(
"https://legacy.service.com/data.xml",
HttpMethod.GET,
entity,
MyXmlResponse.class
);
Note: Our ObjectMapper
(or JAXB if XML) must be correctly configured to handle XML.
4. Combine with Interceptors for Reusability
In large systems, we can create an interceptor that dynamically adjusts content negotiation based on context or configuration.
public class MediaTypeInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
request.getHeaders().setContentType(MediaType.APPLICATION_JSON);
request.getHeaders().setAccept(List.of(MediaType.APPLICATION_JSON));
return execution.execute(request, body);
}
}
Register globally:
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getInterceptors().add(new MediaTypeInterceptor());
return restTemplate;
}
5. Accept Multiple Media Types
Sometimes, services support multiple response formats:
headers.setAccept(Arrays.asList(
MediaType.APPLICATION_JSON,
MediaType.APPLICATION_XML
));
This makes the request more flexible — the server can return what it prefers among those listed.
Reference: Required Dependencies & Import Statements
Maven Dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Common Import Statements
Below are the commonly used import statements for the examples in this guide:
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.RestClientException;
import java.util.Base64;
import java.util.Map;
import java.util.Collections;
// For interceptors
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
Last updated