When building microservices or REST-based applications, it’s common to make HTTP calls from one service to another. In Spring Boot, the traditional and widely used approach for this (before WebClient) is RestTemplate. It provides a simple, synchronous way to perform GET, POST, PUT, DELETE, and other HTTP requests. In this post, we’ll explore practical examples of using RestTemplate, including sending custom headers, handling responses, and performing CRUD operations between services like a Customer Service and an Account Service.
What is RestTemplate?
RestTemplate is a synchronous client provided by the Spring Framework to make HTTP requests to RESTful web services.
It handles HTTP communication and internally relies on java.net.HttpURLConnection, simplifying interaction with external services.
It offers convenience, flexibility, and seamless integration for various HTTP operations. Think of it as a Java client that enables your Spring Boot application to consume REST APIs easily.
Key Features of RestTemplate
1. Synchronous HTTP Calls
RestTemplate executes HTTP requests synchronously, meaning the thread waits for the response before continuing. It’s simple and widely used for service-to-service communication.
2. Multiple HTTP Methods
Supports GET, POST, PUT, PATCH, DELETE, and OPTIONS, making it easy to perform full CRUD operations.
3. Flexible Response Handling
Fetch just the response body (getForObject()), access the full response with status and headers (getForEntity()), or use exchange() for complete control over requests and responses.
4. URI Template Support
Use placeholders ({id}) in URLs to dynamically insert values.
5. Request Body Handling
Send Java objects as JSON or XML and automatically map responses to Java objects.
6. Custom Headers
Add headers using HttpEntity and HttpHeaders for authentication, tracing, or extra metadata.
7. Exception Handling
RestTemplate throws exceptions for HTTP errors (4xx, 5xx) which can be caught or customized with ResponseErrorHandler.
8. Interceptors and Request Factories
Use ClientHttpRequestInterceptor for logging or authentication. Swap the underlying HTTP client via ClientHttpRequestFactory for timeouts, pooling, or advanced configuration.
Setting Up RestTemplate in Spring Boot
Add Dependency
If you’re using Spring Boot Starter Web, RestTemplate is already included. Otherwise, add this to your pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Define RestTemplate as a Bean
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
Inject RestTemplate
@Service
public class AccountService {
private RestTemplate restTemplate;
public AccountService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
}
Common Methods of RestTemplate
1. GET Operations :
- getForObject() : Fetches a resource as an object.
Service:
public CustomerDto getAccountWithCustomer(Long customerId) {
String CUSTOMER_SERVICE_URL = "http://localhost:8081/customers";
return restTemplate.getForObject(CUSTOMER_SERVICE_URL + "/{id}",
CustomerDto.class,
customerId);
}
Controller:
@GetMapping("/{id}")
public CustomerDto getAccountWithCustomer(@PathVariable Long id) {
return accountService.getAccountWithCustomer(id);
}
cURL:
curl --location 'http://localhost:8082/accounts/5' \
--data ''
- getForEntity() : Fetches a resource with HTTP status and headers.
Service:
public CustomerDto getCustomerWithGetForEntity(Long customerId) {
String CUSTOMER_SERVICE_URL = "http://localhost:8081/customers";
ResponseEntity<CustomerDto> response =
restTemplate.getForEntity(
CUSTOMER_SERVICE_URL + "/{id}",
CustomerDto.class,
customerId
);
System.out.println("Status Code: " + response.getStatusCode());
System.out.println("Headers: " + response.getHeaders());
return response.getBody();
}
Controller:
@GetMapping("/cust/{id}")
public CustomerDto getCustomerWithGetForEntity(@PathVariable Long id) {
return accountService.getCustomerWithGetForEntity(id);
}
cURL:
curl --location 'http://localhost:8082/accounts/cust/5'
- exchange(): Flexible method for any HTTP method with headers, body, and status.
Service:
public CustomerDto getCustomerWithExchange(Long customerId) {
HttpHeaders headers = new HttpHeaders();
headers.set("Custom-Header", "customer");
HttpEntity<Void> entity = new HttpEntity<>(headers);
ResponseEntity<CustomerDto> response = restTemplate.exchange(
CUSTOMER_SERVICE_URL + "/{id}",
HttpMethod.GET,
entity,
CustomerDto.class,
customerId
);
System.out.println("Status Code: " + response.getStatusCode());
System.out.println("Headers: " + response.getHeaders());
return response.getBody();
}
Controller:
@GetMapping("/exchange/{id}")
public CustomerDto getCustomerWithExchange(@PathVariable Long id) {
return accountService.getCustomerWithExchange(id);
}
cURL:
curl --location 'http://localhost:8082/accounts/exchange/2' \
--header 'Custom-Header: customer' \
--data ''
2. POST Operations:
- postForObject() : Sends a POST request and retrieves the response body.
Service:
public CustomerDto createCustomerWithPostForObject(CustomerDto customer) {
String CUSTOMER_SERVICE_URL = "http://localhost:8081/customers/create";
return restTemplate.postForObject(CUSTOMER_SERVICE_URL,
customer,
CustomerDto.class);
}
Controller:
@PostMapping("/cust")
public CustomerDto createCustomerWithPostForObject(@RequestBody CustomerDto customer) {
return accountService.createCustomerWithPostForObject(customer);
}
cURL:
curl --location 'http://localhost:8082/accounts/cust' \
--header 'Content-Type: application/json' \
--data-raw '{
"firstName": "Lara",
"lastName": "Jean",
"email": "lara@example.com",
"mobileNo": "09873455672"
}'
- postForEntity() : Sends a POST request and returns a ResponseEntity containing status, headers, and body.
Service:
public CustomerDto createCustomerWithPostForEntity(CustomerDto customer) {
String CUSTOMER_SERVICE_URL = "http://localhost:8081/customers/create";
ResponseEntity<CustomerDto> response =
restTemplate.postForEntity(
CUSTOMER_SERVICE_URL, customer, CustomerDto.class
);
System.out.println("Status Code: " + response.getStatusCode());
return response.getBody();
}
Controller:
@PostMapping("/createCustomer")
public CustomerDto createCustomerWithPostForEntity(@RequestBody CustomerDto customer) {
return accountService.createCustomerWithPostForEntity(customer);
}
cURL:
curl --location 'http://localhost:8082/accounts/customer' \
--header 'Content-Type: application/json' \
--data-raw ' {
"firstName": "Martin",
"lastName": "Wood",
"email": "wood@example.com",
"mobileNo": "9876655443"
}'
3. PUT Operation:
- put() : Updates an existing resource.
Service:
public String updateCustomer(CustomerDto updatedCustomer) {
String CUSTOMER_SERVICE_URL = "http://localhost:8081/customers/update";
restTemplate.put(CUSTOMER_SERVICE_URL,
updatedCustomer);
return "Customer Updated Successfully...!";
}
Controller:
@PutMapping("/customer")
public void updateCustomer(@RequestBody CustomerDto customer) {
accountService.updateCustomer(customer);
}
cURL:
curl --location --request PUT 'http://localhost:8082/accounts/customer' \
--header 'Content-Type: application/json' \
--data-raw ' {
"customerId": "2",
"firstName": "Sam",
"lastName": "Smith",
"email": "sam@gmail.com",
"mobileNo": "9876556651"
}'
4. DELETE Operation:
- delete() : Deletes a resource.
Service:
public String deleteCustomer(Long customerId) {
String CUSTOMER_SERVICE_URL = "http://localhost:8081/customers/delete";
restTemplate.delete(CUSTOMER_SERVICE_URL + "/{id}",
customerId);
return "Customer Deleted Successfully...!";
}
Controller:
@DeleteMapping("/customer/{id}")
public String deleteCustomer(@PathVariable Long id) {
return accountService.deleteCustomer(id);
}
cURL:
curl --location --request DELETE 'http://localhost:8082/accounts/customer/11' \
--data ''
Exception Handling with RestTemplate
When calling external REST APIs using RestTemplate, things don’t always go as expected. Network issues, unavailable services, or server errors can occur, resulting in exceptions.
Proper exception handling ensures your application can gracefully handle errors, provide meaningful messages, and prevent crashes.
In Spring Boot, you can handle exceptions locally within service methods or globally using @ControllerAdvice and @ExceptionHandler.
Common Exceptions
HttpClientErrorException : 4xx HTTP errors (e.g., 404 Not Found, 400 Bad Request)
HttpServerErrorException : 5xx HTTP errors (e.g., 500 Internal Server Error)
ResourceAccessException : Network errors, timeouts, or unreachable host
RestClientException : Generic error if none of the above match
Handle Exceptions Locally
Wrap your RestTemplate calls in a try-catch block:
This approach is simple for service-level error handling.
public CustomerDto getCustomerById(Long customerId) {
String CUSTOMER_SERVICE_URL = "http://localhost:8081/customers/{id}";
try {
return restTemplate.getForObject(CUSTOMER_SERVICE_URL, CustomerDto.class, customerId);
} catch (HttpClientErrorException.NotFound e) {
throw new RuntimeException("Customer not found with ID: " + customerId);
} catch (HttpClientErrorException e) {
throw new RuntimeException("Client Error: " + e.getStatusCode());
} catch (HttpServerErrorException e) {
throw new RuntimeException("Server Error: " + e.getStatusCode());
} catch (ResourceAccessException e) {
throw new RuntimeException("Service unreachable: " + e.getMessage());
}
}
Global Exception Handling with @ControllerAdvice
You can also handle exceptions globally in your Spring Boot application.
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(HttpClientErrorException.NotFound.class)
public ResponseEntity<String> handleNotFound(HttpClientErrorException.NotFound ex) {
return new ResponseEntity<>("Resource not found: " + ex.getMessage(),
HttpStatus.NOT_FOUND);
}
@ExceptionHandler(HttpClientErrorException.class)
public ResponseEntity<String> handleClientError(HttpClientErrorException ex) {
return new ResponseEntity<>("Client Error: " + ex.getStatusCode(),
HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(HttpServerErrorException.class)
public ResponseEntity<String> handleServerError(HttpServerErrorException ex) {
return new ResponseEntity<>("Server Error: " + ex.getStatusCode(),
HttpStatus.INTERNAL_SERVER_ERROR);
}
@ExceptionHandler(ResourceAccessException.class)
public ResponseEntity<String> handleResourceAccess(ResourceAccessException ex) {
return new ResponseEntity<>("Service unreachable: " + ex.getMessage(),
HttpStatus.SERVICE_UNAVAILABLE);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleGenericException(Exception ex) {
return new ResponseEntity<>("Error: " + ex.getMessage(),
HttpStatus.INTERNAL_SERVER_ERROR);
}
}
How It Works :
- When AccountService calls CustomerService via RestTemplate, it might throw exceptions like HttpClientErrorException or ResourceAccessException.
- Spring automatically detects these exceptions in the same application context (AccountService) and routes them to your GlobalExceptionHandler.
- You don’t need to write try-catch in every method, unless you want customized local handling.
Connection Pooling with RestTemplate
By default, RestTemplate creates a new HTTP connection for each request. This is fine for low-traffic apps, but under load, it can impact the performance. Using connection pooling allows to reuse connections and improve efficiency.
RestTemplate uses SimpleClientHttpRequestFactory, which:
- Opens a new TCP connection for every HTTP request.
- Closes the connection immediately after the request is done.
Problem:
- Every new connection involves TCP handshake + SSL handshake (if HTTPS) → expensive.
- For high-traffic microservices or APIs, this leads to performance bottlenecks.
Solution: Connection pooling.
- Keep a pool of reusable HTTP connections open.
- Requests reuse idle connections instead of creating new ones.
- Reduces latency and improves throughput.
How RestTemplate Uses Connection Pooling
RestTemplate doesn’t support pooling directly. We need Apache HttpClient.
- Apache HttpClient has PoolingHttpClientConnectionManager that manages reusable connections.
- HttpComponentsClientHttpRequestFactory allows RestTemplate to use this HttpClient.
Add Apache HttpClient Dependency
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
</dependency>
Configure RestTemplate with Connection Pooling
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
// 1. Setup connection pool
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(50);
connectionManager.setDefaultMaxPerRoute(20);
// 2. Build HttpClient with connection manager
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(connectionManager)
.build();
// 3. Create RestTemplate factory
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setHttpClient(httpClient);
factory.setConnectTimeout(5000);
factory.setReadTimeout(5000);
return new RestTemplate(factory);
}
}
- MaxTotal: Maximum connections the pool can hold in total.
- DefaultMaxPerRoute: Maximum connections to a single host.
Example: If your app talks to 3 services, the pool can hold up to 50 connections overall, but a single service will not exceed 20 concurrent connections.
- CloseableHttpClient is a thread-safe HttpClient. It uses the PoolingHttpClientConnectionManager internally.
- Connect Timeout: Max time to establish TCP connection.
- Read Timeout: Max time to wait for the response after connection.
This ensures that your app doesn’t hang indefinitely if a service is slow or unresponsive.
Benefits of Using Connection Pooling
- Reduced latency : Avoids repeated TCP/SSL handshakes.
- Improved throughput : Handles more requests simultaneously.
- Resource efficiency : Less CPU and network usage.
- Stability under load : Prevents connection exhaustion during spikes.
Note on Deprecation
RestTemplate has been officially deprecated by the Spring team and is no longer being actively developed. The recommended alternative is WebClient, which offers non-blocking, reactive, and more scalable HTTP communication.
That said, RestTemplate is still heavily used in real-world projects and widely found in production codebases. Understanding it is essential if you’re maintaining existing applications or working in environments that haven’t fully migrated to WebClient.
Conclusion
We explored RestTemplate in depth — from its features and flexible methods to practical CRUD examples, handling custom headers, and managing exceptions. RestTemplate has long been the go-to tool for synchronous HTTP communication in Spring Boot, making it easy to connect services in a microservice architecture.
Takeaway: RestTemplate may be deprecated, but with proper setup — including exception handling and connection pooling — it remains a powerful tool in legacy systems, while WebClient leads the way for modern applications.
Top comments (0)