Starting Spring Framework 6.1 and Sring Boot 3.2, we can use the Spring RestClient for performing HTTP requests using a fluent and synchronous API. The RestClient works over the underlying HTTP client libraries such the JDK HttpClient, Apache HttpComponents, and others.
As the name suggests, RestClient offers the fluent API design of WebClient and the functionality of RestTemplate. The RestClient is designed with testability in mind, making it easier to mock HTTP interactions in unit tests.
Note that for asynchronous and streaming scenarios, WebClient is still the preferred API.
1. Choosing between RestTemplate, RestClient and WebClient
Note that as of Spring 6.1, in comparison to RestTemplate, the RestClient offers a more modern API for synchronous HTTP access. RestTemplate, added in Spring 3, is a bloated class exposing every capability of HTTP in a template-like class with too many overloaded methods.
The WebClient also supports synchronous HTTP access, but it requires an additional dependency spring-boot-starter-webflux. We can avoid adding a new dependency in the project by adding RestClient.
In simple words,
- RestTemplate is older, synchronous API for making HTTP requests. It lacks the flexibility and modern features that newer applications may require.
- WebClient is reactive, non-blocking client part of Spring WebFlux. Although it can be used for synchronous interactions, but mostly it seems an overkill for simple usecases.
- RestClient is the new addition to Spring framework and intends to replace the RestTemplate. It provides a more modern, fluent API like WebClient but without requiring a reactive stack thus making it a middle ground between RestTemplate and WebClient.
See also: Spring RestTemplate vs WebClient
2. Maven
The RestClient is part of the Spring Web module so include it in the application. It is available in Spring Framework 6.1 onwards.
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>6.1</version>
<scope>compile</scope>
</dependency>
In Spring boot applications, we can transitively include all the necessary dependencies with the web starter dependency. The supported version (GA) of Spring Boot is 3.2.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
If we are using the HttpClient for underlying HTTP access, we need to add those dependencies additionally.
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.2.1</version>
</dependency>
3. Creating RestClient
Spring allows several flexible ways to initialize a RestClient bean. For example, the simplest method is to use the create() method.
import org.springframework.web.client.RestClient;
//...
@Value("${REMOTE_BASE_URI:http://localhost:3000}")
String baseURI;
@Bean
RestClient restClient() {
return RestClient.create(baseURI);
}
We can also use the builder() method which allows us to set more complex options such as default headers, request processors, message handlers etc. For example, the following configuration uses the HttpClient as the underlying library for HTTP connection management.
@Autowired
CloseableHttpClient httpClient;
@Bean
RestClient restClient() {
return RestClient.builder()
.baseUrl(baseURI)
//.requestInterceptor(...)
//.defaultHeader("AUTHORIZATION", fetchToken())
//.messageConverters(...)
.requestFactory(clientHttpRequestFactory())
.build();
}
@Bean
public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory() {
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory
= new HttpComponentsClientHttpRequestFactory();
clientHttpRequestFactory.setHttpClient(httpClient);
return clientHttpRequestFactory;
}
Finally, in an existing application, where we have been using RestTemplate for HTTP communication, we can reuse HTTP configuration in RestClient bean.
@Bean
RestClient restClient() {
return RestClient.create(restTemplate());
}
4. HTTP GET
The restClient.get() is used to create a GET request to the specified URL. Note that we can pass the dynamic values to URI templates.
restClient.get()
.uri("/employees")
//...
restClient.get()
.uri("/employees/{id}", id)
//...
Finally, the retrieve() method sends the request and returns the ResponseSpec containing the API response. The following request retrieves a list of employees and parses the response body to List of Employee instances.
List<Employee> employeeList = restClient.get()
.uri("/employees")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.body(List.class);
Assertions.assertNotNull(employeeList);
If we are interested in handling the other response parts, such as status, headers etc, we can fetch the entity as ResponseEntity as follows:
ResponseEntity<List> responseEntity = restClient.get()
.uri("/employees")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.toEntity(List.class);
Assertions.assertNotNull(responseEntity.getBody());
Assertions.assertEquals(HttpStatus.OK.value(), responseEntity.getStatusCode().value());
Assertions.assertNotEquals(null, responseEntity.getHeaders());
5. HTTP POST
The restClient.post() is used to create a POST request to the specified URL. Most things remain the same as the GET API calls, except the POST APIs generally do not return any response. They only return the response code 201 CREATED
and the location of the created resource.
The toBodilessEntity() method serves the exact same purpose and can be used in POST APIs.
Employee newEmployee = new Employee(5l, "Amit", "active");
ResponseEntity<Void> responseEntity = restClient.post()
.uri("/employees")
.contentType(MediaType.APPLICATION_JSON)
.body(newEmployee)
.retrieve()
.toBodilessEntity();
Assertions.assertEquals(HttpStatus.CREATED.value(), responseEntity.getStatusCode().value());
Assertions.assertEquals("http://localhost:3000/employees/5",
responseEntity.getHeaders().get("Location").get(0));
6. HTTP PUT
The restClient.put() is used to create a PUT request to the specified URL. The PUT APIs, generally, send a request body and receive a response body. The following is an example of such PUT communication.
ResponseEntity<Employee> responseEntity = restClient.put()
.uri("/employees/1")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.body(updatedEmployee)
.retrieve()
.toEntity(Employee.class);
Assertions.assertEquals(HttpStatus.OK.value(), responseEntity.getStatusCode().value());
Assertions.assertEquals(updatedEmployee.getName(), responseEntity.getBody().getName());
7. HTTP DELETE
The restClient.delete() is used to create a DELETE request to the specified URL. Generally, delete APIs are accepted in the server and do not request a response body.
ResponseEntity<Employee> responseEntity = restClient.delete()
.uri("/employees/5")
.retrieve()
.toBodilessEntity();
Assertions.assertEquals(HttpStatus.OK.value(), responseEntity.getStatusCode().value());
8. RestClient exchange() API
If we need complete control over the response processing, we can employ the exchange() method. It gives access to HttpRequest and HttpResponse objects and then we can use them the way we require.
List<Employee> list = restClient.get()
.uri("/employees")
.accept(MediaType.APPLICATION_JSON)
.exchange((request, response) -> {
List apiResponse = null;
if (response.getStatusCode().is4xxClientError()
|| response.getStatusCode().is5xxServerError()) {
Assertions.fail("Error occurred in test execution. Check test data and api url.");
} else {
ObjectMapper mapper = new ObjectMapper();
apiResponse = mapper.readValue(response.getBody(), List.class);
}
return apiResponse;
});
Assertions.assertEquals(4, list.size());
9. Exception Handling
The RestClient throws two types of exceptions for a failed request:
- HttpClientErrorException: with 4xx response code
- HttpServerErrorException: with 5xx response code
HttpClientErrorException thrown = Assertions.assertThrows(HttpClientErrorException.class,
() -> {
Employee employee = restClient.get()
.uri("/employees/500")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.body(Employee.class);
});
Assertions.assertEquals(404, thrown.getStatusCode().value());
10. Conclusion
This Spring RectClient tutorial briefly introduces the core methods for performing HTTP requests and handling the responses in various ways. We can consider using the RestClient over RestTemplate for synchronous HTTP requests.
Using WebClient is still the recommended approach for asynchronous communications.
Happy Learning !!
Reference: Spring Rest Clients
Comments