DEV Community

Cover image for Mastering RestTemplate in Spring Boot
pallavi kamble
pallavi kamble

Posted on

Mastering RestTemplate in Spring Boot

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>
Enter fullscreen mode Exit fullscreen mode

Define RestTemplate as a Bean

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

}
Enter fullscreen mode Exit fullscreen mode

Inject RestTemplate

@Service
public class AccountService {

    private RestTemplate restTemplate;

    public AccountService(RestTemplate restTemplate) {  
        this.restTemplate = restTemplate;
    }
}
Enter fullscreen mode Exit fullscreen mode

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);

    }
Enter fullscreen mode Exit fullscreen mode

Controller:

@GetMapping("/{id}")
    public CustomerDto getAccountWithCustomer(@PathVariable Long id) {
        return accountService.getAccountWithCustomer(id);   
    }
Enter fullscreen mode Exit fullscreen mode

cURL:

curl --location 'http://localhost:8082/accounts/5' \
--data ''
Enter fullscreen mode Exit fullscreen mode

- 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();
    }

Enter fullscreen mode Exit fullscreen mode

Controller:

@GetMapping("/cust/{id}")
    public CustomerDto getCustomerWithGetForEntity(@PathVariable Long id) {
        return accountService.getCustomerWithGetForEntity(id);  
    }
Enter fullscreen mode Exit fullscreen mode

cURL:

curl --location 'http://localhost:8082/accounts/cust/5'
Enter fullscreen mode Exit fullscreen mode

- 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();
    }
Enter fullscreen mode Exit fullscreen mode

Controller:

@GetMapping("/exchange/{id}")
    public CustomerDto getCustomerWithExchange(@PathVariable Long id) {
        return accountService.getCustomerWithExchange(id);
    }
Enter fullscreen mode Exit fullscreen mode

cURL:

curl --location 'http://localhost:8082/accounts/exchange/2' \
--header 'Custom-Header: customer' \
--data ''
Enter fullscreen mode Exit fullscreen mode

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);
    }
Enter fullscreen mode Exit fullscreen mode

Controller:

@PostMapping("/cust")
    public CustomerDto createCustomerWithPostForObject(@RequestBody CustomerDto customer) {
        return accountService.createCustomerWithPostForObject(customer);
    }
Enter fullscreen mode Exit fullscreen mode

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"
         }'
Enter fullscreen mode Exit fullscreen mode

- 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();
    } 
Enter fullscreen mode Exit fullscreen mode

Controller:

@PostMapping("/createCustomer")
    public CustomerDto createCustomerWithPostForEntity(@RequestBody CustomerDto customer) {
        return accountService.createCustomerWithPostForEntity(customer);
    }
Enter fullscreen mode Exit fullscreen mode

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"
         }'
Enter fullscreen mode Exit fullscreen mode

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...!";
    }
Enter fullscreen mode Exit fullscreen mode

Controller:

@PutMapping("/customer")
    public void updateCustomer(@RequestBody CustomerDto customer) {
        accountService.updateCustomer(customer);
    }
Enter fullscreen mode Exit fullscreen mode

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"
}'
Enter fullscreen mode Exit fullscreen mode

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...!";
    } 
Enter fullscreen mode Exit fullscreen mode

Controller:

 @DeleteMapping("/customer/{id}")
    public String deleteCustomer(@PathVariable Long id) {
        return accountService.deleteCustomer(id);
    }
Enter fullscreen mode Exit fullscreen mode

cURL:

curl --location --request DELETE 'http://localhost:8082/accounts/customer/11' \
--data ''
Enter fullscreen mode Exit fullscreen mode

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());
        }
    }
Enter fullscreen mode Exit fullscreen mode

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);
    }
}


Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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);

    }
}
Enter fullscreen mode Exit fullscreen mode
  • 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)