This source did not publish a separate summary. Review SKILL.md before using the skill.
SKILL.md
RestTemplate to RestClient Migration Skill
Overview
Spring Framework 6.1 (Spring Boot 3.2+) introduces RestClient, a modern, fluent API for synchronous HTTP requests that replaces the older RestTemplate. While RestTemplate still works, RestClient is the recommended approach for new code.
Key Differences
Feature
RestTemplate
RestClient
API Style
Template methods
Fluent builder
Configuration
Constructor injection
Builder pattern
Error handling
ResponseErrorHandler
Status handlers
Type safety
Limited
Better with generics
Migration Examples
1. Basic GET Request
Before (RestTemplate)
@Service
public class ExternalApiService {
private final RestTemplate restTemplate;
public ExternalApiService() {
this.restTemplate = new RestTemplate();
}
public Map<String, Object> getUser(String userId) {
String url = "https://api.example.com/users/" + userId;
ResponseEntity<Map> response = restTemplate.getForEntity(url, Map.class);
return response.getBody();
}
}
After (RestClient)
@Service
public class ExternalApiService {
private final RestClient restClient;
public ExternalApiService() {
this.restClient = RestClient.create();
}
public Map<String, Object> getUser(String userId) {
return restClient.get()
.uri("https://api.example.com/users/{id}", userId)
.retrieve()
.body(new ParameterizedTypeReference<Map<String, Object>>() {});
}
}
@Configuration
public class RestClientConfig {
@Value("${external.api.base-url}")
private String baseUrl;
@Bean
public RestClient restClient() {
return RestClient.builder()
.baseUrl(baseUrl)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.build();
}
}
Using the Configured RestClient
@Service
public class ExternalApiService {
private final RestClient restClient;
public ExternalApiService(RestClient restClient) {
this.restClient = restClient;
}
// Methods can now use relative URIs
public Map<String, Object> getUser(String userId) {
return restClient.get()
.uri("/users/{id}", userId)
.retrieve()
.body(new ParameterizedTypeReference<Map<String, Object>>() {});
}
}
Error Handling
RestClient Status Handlers
public Map<String, Object> getUserWithErrorHandling(String userId) {
return restClient.get()
.uri("/users/{id}", userId)
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, (request, response) -> {
throw new UserNotFoundException("User not found: " + userId);
})
.onStatus(HttpStatusCode::is5xxServerError, (request, response) -> {
throw new ExternalServiceException("External service error");
})
.body(new ParameterizedTypeReference<Map<String, Object>>() {});
}
Type-Safe Responses
Using ParameterizedTypeReference
// For generic types like Map or List
Map<String, Object> map = restClient.get()
.uri("/data")
.retrieve()
.body(new ParameterizedTypeReference<Map<String, Object>>() {});
List<User> users = restClient.get()
.uri("/users")
.retrieve()
.body(new ParameterizedTypeReference<List<User>>() {});
Direct Class Mapping
// For simple types
User user = restClient.get()
.uri("/users/{id}", userId)
.retrieve()
.body(User.class);
String text = restClient.get()
.uri("/text")
.retrieve()
.body(String.class);
Complete Service Migration Example
Before
@Service
public class ExternalApiService {
private final RestTemplate restTemplate;
@Value("${external.api.base-url}")
private String baseUrl;
public ExternalApiService() {
this.restTemplate = new RestTemplate();
}
public boolean verifyEmail(String email) {
try {
String url = baseUrl + "/verify/email?email=" + email;
ResponseEntity<Map> response = restTemplate.getForEntity(url, Map.class);
return Boolean.TRUE.equals(response.getBody().get("valid"));
} catch (Exception e) {
return false;
}
}
}