Spring 6 RestClient (with Examples)

Starting Spring Framework 6.1 (and Spring Boot 3.2.0-M1), we can use the Spring RestClient interface 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.

Spring RestClient is a preview feature in Spring Boot 3.2 (to be released in Nov’23). For now, you can play with using the Milestone releases, to experience what’s coming.

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 required an additional dependency spring-boot-starter-webflux. We can avoid adding a new dependency in the project by adding RestClient.

Note that for asynchronous and streaming scenarios, WebClient is still the preferred API.

In simple words, if the project requires:

  • only the synchronous access to remote APIs, include the spring-boot-starter-web dependency and prefer using the RestClient over RestTemplate.
  • the asynchronous access to remote APIs, include the spring-boot-starter-webflux dependency and prefer using the WebClient for all needs.

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.

@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 !!

Source Code on Github

Comments

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.