
Spring RestTemplate Example – How to Make HTTP Requests
Spring’s RestTemplate has been the go-to solution for making HTTP requests in Spring applications for years, though it’s now in maintenance mode with WebClient being the preferred async alternative. RestTemplate provides a synchronous client-side HTTP access library that simplifies communication with RESTful services through a variety of HTTP methods. You’ll learn how to configure RestTemplate properly, handle different HTTP methods, manage request/response transformations, implement error handling, and apply best practices for production environments.
Understanding RestTemplate Architecture
RestTemplate operates as a synchronous HTTP client built on top of Java’s HttpURLConnection or other HTTP client libraries like Apache HttpClient. It uses a template method pattern where you define the HTTP operation, and RestTemplate handles the underlying connection management, request/response marshalling, and resource cleanup.
The core components include:
- ClientHttpRequestFactory – Creates HTTP requests
- HttpMessageConverter – Handles request/response body conversion
- ResponseErrorHandler – Manages HTTP error responses
- ClientHttpRequestInterceptor – Provides request/response interception
RestTemplate supports automatic JSON/XML serialization through Jackson or JAXB, making it seamless to work with POJOs instead of raw HTTP data.
Basic RestTemplate Setup and Configuration
First, ensure you have the necessary dependencies in your project. For Maven:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.21</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.3</version>
</dependency>
Create a basic RestTemplate configuration:
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
// Configure timeout settings
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setConnectTimeout(5000);
factory.setReadTimeout(10000);
restTemplate.setRequestFactory(factory);
return restTemplate;
}
@Bean
public RestTemplate customRestTemplate() {
RestTemplate restTemplate = new RestTemplate();
// Add custom error handler
restTemplate.setErrorHandler(new CustomResponseErrorHandler());
// Add interceptors for logging or authentication
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
interceptors.add(new LoggingRequestInterceptor());
restTemplate.setInterceptors(interceptors);
return restTemplate;
}
}
HTTP Methods Implementation Examples
Here’s a comprehensive service class demonstrating various HTTP operations:
@Service
public class ApiService {
@Autowired
private RestTemplate restTemplate;
private static final String BASE_URL = "https://jsonplaceholder.typicode.com";
// GET request with path variables
public User getUserById(Long id) {
String url = BASE_URL + "/users/{id}";
return restTemplate.getForObject(url, User.class, id);
}
// GET request with query parameters
public List<Post> getPostsByUserId(Long userId) {
String url = BASE_URL + "/posts?userId={userId}";
ResponseEntity<Post[]> response = restTemplate.getForEntity(url, Post[].class, userId);
return Arrays.asList(response.getBody());
}
// POST request with request body
public Post createPost(Post post) {
String url = BASE_URL + "/posts";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Post> requestEntity = new HttpEntity<>(post, headers);
return restTemplate.postForObject(url, requestEntity, Post.class);
}
// PUT request for updates
public void updatePost(Long id, Post post) {
String url = BASE_URL + "/posts/{id}";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Post> requestEntity = new HttpEntity<>(post, headers);
restTemplate.put(url, requestEntity, id);
}
// DELETE request
public void deletePost(Long id) {
String url = BASE_URL + "/posts/{id}";
restTemplate.delete(url, id);
}
// Generic exchange method for complex scenarios
public ResponseEntity<String> makeCustomRequest() {
String url = BASE_URL + "/posts/1";
HttpHeaders headers = new HttpHeaders();
headers.add("Custom-Header", "CustomValue");
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
HttpEntity<String> entity = new HttpEntity<>(headers);
return restTemplate.exchange(
url,
HttpMethod.GET,
entity,
String.class
);
}
}
Supporting POJO classes:
public class User {
private Long id;
private String name;
private String email;
private String phone;
// constructors, getters, setters
}
public class Post {
private Long id;
private Long userId;
private String title;
private String body;
// constructors, getters, setters
}
Advanced Configuration and Error Handling
Implement robust error handling and custom configurations:
public class CustomResponseErrorHandler implements ResponseErrorHandler {
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return response.getStatusCode().series() == HttpStatus.Series.CLIENT_ERROR ||
response.getStatusCode().series() == HttpStatus.Series.SERVER_ERROR;
}
@Override
public void handleError(ClientHttpResponse response) throws IOException {
HttpStatus statusCode = response.getStatusCode();
switch (statusCode.series()) {
case CLIENT_ERROR:
if (statusCode == HttpStatus.NOT_FOUND) {
throw new ResourceNotFoundException("Resource not found");
} else if (statusCode == HttpStatus.BAD_REQUEST) {
throw new BadRequestException("Bad request: " + getResponseBody(response));
}
break;
case SERVER_ERROR:
throw new ServerException("Server error: " + statusCode);
default:
throw new RestClientException("Unknown error: " + statusCode);
}
}
private String getResponseBody(ClientHttpResponse response) throws IOException {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(response.getBody()))) {
return reader.lines().collect(Collectors.joining("\n"));
}
}
}
public class LoggingRequestInterceptor implements ClientHttpRequestInterceptor {
private static final Logger logger = LoggerFactory.getLogger(LoggingRequestInterceptor.class);
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
logger.info("Request URI: {}", request.getURI());
logger.info("Request Method: {}", request.getMethod());
logger.info("Request Headers: {}", request.getHeaders());
if (body.length > 0) {
logger.info("Request Body: {}", new String(body, StandardCharsets.UTF_8));
}
ClientHttpResponse response = execution.execute(request, body);
logger.info("Response Status: {}", response.getStatusCode());
logger.info("Response Headers: {}", response.getHeaders());
return response;
}
}
Authentication and Security Implementation
Handle various authentication schemes:
@Service
public class SecureApiService {
@Autowired
private RestTemplate restTemplate;
// Basic Authentication
public String callSecureEndpointBasicAuth(String username, String password) {
String url = "https://api.example.com/secure-data";
HttpHeaders headers = new HttpHeaders();
String auth = username + ":" + password;
byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes(StandardCharsets.UTF_8));
String authHeader = "Basic " + new String(encodedAuth);
headers.set("Authorization", authHeader);
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<String> response = restTemplate.exchange(
url, HttpMethod.GET, entity, String.class);
return response.getBody();
}
// Bearer Token Authentication
public ApiResponse callWithBearerToken(String token, Object requestData) {
String url = "https://api.example.com/protected-resource";
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(token);
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Object> entity = new HttpEntity<>(requestData, headers);
return restTemplate.postForObject(url, entity, ApiResponse.class);
}
// Custom header authentication
public void callWithApiKey(String apiKey) {
String url = "https://api.example.com/data";
HttpHeaders headers = new HttpHeaders();
headers.add("X-API-Key", apiKey);
headers.add("X-Client-Version", "1.0");
HttpEntity<String> entity = new HttpEntity<>(headers);
restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
}
}
Performance Optimization and Connection Pooling
Configure Apache HttpClient for better performance:
@Configuration
public class HighPerformanceRestTemplateConfig {
@Bean
public RestTemplate highPerformanceRestTemplate() {
// Configure connection pooling
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(100);
connectionManager.setDefaultMaxPerRoute(20);
// Configure timeouts and retry policy
RequestConfig requestConfig = RequestConfig.custom()
.setConnectionRequestTimeout(5000)
.setConnectTimeout(5000)
.setSocketTimeout(10000)
.build();
// Build HttpClient with custom configuration
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(connectionManager)
.setDefaultRequestConfig(requestConfig)
.setRetryHandler(new DefaultHttpRequestRetryHandler(3, true))
.build();
// Create RestTemplate with HttpClient
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
RestTemplate restTemplate = new RestTemplate(factory);
// Add compression support
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
messageConverters.add(new StringHttpMessageConverter());
messageConverters.add(new MappingJackson2HttpMessageConverter());
restTemplate.setMessageConverters(messageConverters);
return restTemplate;
}
}
RestTemplate vs Alternatives Comparison
Feature | RestTemplate | WebClient | OkHttp | Apache HttpClient |
---|---|---|---|---|
Execution Model | Synchronous | Asynchronous/Reactive | Sync/Async | Synchronous |
Spring Integration | Native | Native (Spring 5+) | Third-party | Third-party |
Performance | Good | Excellent | Excellent | Good |
Memory Usage | Medium | Low | Low | Medium |
Learning Curve | Easy | Moderate | Easy | Moderate |
Maintenance Status | Maintenance Mode | Active Development | Active | Active |
Real-World Use Cases and Integration Patterns
Implement a service that integrates with multiple external APIs:
@Service
public class IntegrationService {
@Autowired
private RestTemplate restTemplate;
@Value("${payment.api.url}")
private String paymentApiUrl;
@Value("${notification.api.url}")
private String notificationApiUrl;
@Retryable(value = {ResourceAccessException.class}, maxAttempts = 3, backoff = @Backoff(delay = 1000))
public PaymentResponse processPayment(PaymentRequest request) {
String url = paymentApiUrl + "/process";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setBearerAuth(getPaymentApiToken());
HttpEntity<PaymentRequest> entity = new HttpEntity<>(request, headers);
try {
ResponseEntity<PaymentResponse> response = restTemplate.exchange(
url, HttpMethod.POST, entity, PaymentResponse.class);
// Log successful payment
auditPayment(request, response.getBody());
return response.getBody();
} catch (HttpClientErrorException e) {
if (e.getStatusCode() == HttpStatus.BAD_REQUEST) {
throw new InvalidPaymentException("Invalid payment data: " + e.getResponseBodyAsString());
}
throw new PaymentProcessingException("Payment failed: " + e.getMessage());
}
}
@Async
public CompletableFuture<Void> sendNotificationAsync(NotificationRequest notification) {
return CompletableFuture.runAsync(() -> {
String url = notificationApiUrl + "/send";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.add("X-API-Key", getNotificationApiKey());
HttpEntity<NotificationRequest> entity = new HttpEntity<>(notification, headers);
try {
restTemplate.postForEntity(url, entity, String.class);
} catch (Exception e) {
// Log error but don't fail the main process
log.error("Failed to send notification: {}", e.getMessage());
}
});
}
// Implement circuit breaker pattern
@CircuitBreaker(name = "external-service", fallbackMethod = "fallbackGetData")
public ExternalData getExternalData(String id) {
String url = "https://external-api.com/data/{id}";
return restTemplate.getForObject(url, ExternalData.class, id);
}
public ExternalData fallbackGetData(String id, Exception ex) {
// Return cached data or default response
return getCachedData(id).orElse(new ExternalData("default", "Fallback data"));
}
}
Testing RestTemplate Interactions
Comprehensive testing strategies for RestTemplate-based services:
@ExtendWith(MockitoExtension.class)
class ApiServiceTest {
@Mock
private RestTemplate restTemplate;
@InjectMocks
private ApiService apiService;
@Test
void testGetUserById() {
// Given
Long userId = 1L;
User expectedUser = new User(userId, "John Doe", "john@example.com");
String expectedUrl = "https://jsonplaceholder.typicode.com/users/{id}";
when(restTemplate.getForObject(expectedUrl, User.class, userId))
.thenReturn(expectedUser);
// When
User actualUser = apiService.getUserById(userId);
// Then
assertThat(actualUser).isNotNull();
assertThat(actualUser.getName()).isEqualTo("John Doe");
verify(restTemplate).getForObject(expectedUrl, User.class, userId);
}
@Test
void testCreatePost() {
// Given
Post inputPost = new Post(null, 1L, "Test Title", "Test Body");
Post expectedPost = new Post(101L, 1L, "Test Title", "Test Body");
String expectedUrl = "https://jsonplaceholder.typicode.com/posts";
ArgumentCaptor<HttpEntity<Post>> entityCaptor = ArgumentCaptor.forClass(HttpEntity.class);
when(restTemplate.postForObject(eq(expectedUrl), entityCaptor.capture(), eq(Post.class)))
.thenReturn(expectedPost);
// When
Post actualPost = apiService.createPost(inputPost);
// Then
assertThat(actualPost.getId()).isEqualTo(101L);
HttpEntity<Post> capturedEntity = entityCaptor.getValue();
assertThat(capturedEntity.getBody()).isEqualTo(inputPost);
assertThat(capturedEntity.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON);
}
}
// Integration testing with MockRestServiceServer
@SpringBootTest
class ApiServiceIntegrationTest {
@Autowired
private ApiService apiService;
@Autowired
private RestTemplate restTemplate;
private MockRestServiceServer mockServer;
@BeforeEach
void setUp() {
mockServer = MockRestServiceServer.createServer(restTemplate);
}
@Test
void testGetUserById_IntegrationTest() throws Exception {
// Given
String expectedUrl = "https://jsonplaceholder.typicode.com/users/1";
String responseJson = "{\"id\":1,\"name\":\"John Doe\",\"email\":\"john@example.com\"}";
mockServer.expect(requestTo(expectedUrl))
.andExpect(method(HttpMethod.GET))
.andRespond(withSuccess(responseJson, MediaType.APPLICATION_JSON));
// When
User user = apiService.getUserById(1L);
// Then
assertThat(user.getName()).isEqualTo("John Doe");
mockServer.verify();
}
}
Common Pitfalls and Best Practices
Avoid these frequent mistakes when working with RestTemplate:
- Resource Leaks: Always configure proper connection pooling and timeouts to prevent resource exhaustion
- Exception Handling: Don’t rely on default error handling; implement custom ResponseErrorHandler for better control
- Blocking Operations: Remember RestTemplate is synchronous and will block threads; consider WebClient for reactive applications
- Hardcoded URLs: Use configuration properties and UriComponentsBuilder for dynamic URL construction
- Missing Headers: Always set appropriate Content-Type and Accept headers
- Security: Never log sensitive data like authentication tokens or passwords
Performance-focused configuration example:
@ConfigurationProperties(prefix = "app.rest-client")
@Data
public class RestClientProperties {
private int connectTimeout = 5000;
private int readTimeout = 30000;
private int maxConnections = 100;
private int maxConnectionsPerRoute = 20;
private boolean compressionEnabled = true;
}
@Configuration
@EnableConfigurationProperties(RestClientProperties.class)
public class OptimizedRestTemplateConfig {
@Bean
@ConditionalOnProperty(name = "app.rest-client.enabled", havingValue = "true", matchIfMissing = true)
public RestTemplate optimizedRestTemplate(RestClientProperties properties) {
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(properties.getMaxConnections());
connectionManager.setDefaultMaxPerRoute(properties.getMaxConnectionsPerRoute());
RequestConfig requestConfig = RequestConfig.custom()
.setConnectionRequestTimeout(properties.getConnectTimeout())
.setConnectTimeout(properties.getConnectTimeout())
.setSocketTimeout(properties.getReadTimeout())
.build();
HttpClientBuilder clientBuilder = HttpClients.custom()
.setConnectionManager(connectionManager)
.setDefaultRequestConfig(requestConfig);
if (properties.isCompressionEnabled()) {
clientBuilder.addInterceptorFirst(new RequestAcceptEncoding())
.addInterceptorFirst(new ResponseContentEncoding());
}
CloseableHttpClient httpClient = clientBuilder.build();
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory(httpClient);
return new RestTemplate(factory);
}
}
When deploying RestTemplate-based applications on production servers, ensure your hosting environment can handle the connection load. For high-traffic applications, consider using VPS hosting for better resource control or dedicated servers for maximum performance and isolation.
For additional reference, consult the official Spring RestTemplate documentation and consider migrating to WebClient for new projects to take advantage of reactive programming benefits.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.