In Spring Boot applications, communication with external services through REST APIs is common. Traditionally, this was achieved using RestTemplate, but it is now deprecated as of Spring Framework 6.1 and Spring Boot 3.2. The new RestClient API has been introduced as its modern, fluent, and flexible alternative for making HTTP requests.
What is RestClient?
RestClient is a modern HTTP client introduced in Spring Framework 6.1. It provides a fluent, builder-based API for sending synchronous and asynchronous HTTP requests with cleaner syntax and improved readability. It supports features like request customization, status-based error handling, interceptors, and HTTP/2 communication.
Key Features of RestClient
1. Fluent API Design
The fluent API allows developers to build requests using method chaining, improving code clarity and reducing boilerplate.
Example:
RestClient restClient = RestClient.builder().build();
String response = restClient.get()
.uri("https://api.example.com/resource")
.retrieve()
.body(String.class);
Explanation:
- get(): defines the HTTP method.
- uri(): specifies the target URL.
- retrieve(): executes the request.
- body(String.class): extracts the response as a String.
2. Synchronous and Asynchronous Requests
Synchronous Example:
String response = restClient.get()
.uri("https://api.example.com/data")
.retrieve()
.body(String.class);
This blocks the thread until a response is received.
Asynchronous Example:
RestClient integrates with reactive and asynchronous types like CompletableFuture or Mono.
import java.util.concurrent.CompletableFuture;
public CompletableFuture<String> getAsyncResponse(String url) {
return CompletableFuture.supplyAsync(() ->
restClient.get()
.uri(url)
.retrieve()
.body(String.class)
);
}
3. Error and Status Code Handling
RestClient provides built-in support for handling errors based on HTTP status codes using the onStatus() method.
Example:
import org.springframework.http.HttpStatus;
import reactor.core.publisher.Mono;
String response = restClient.get()
.uri("https://api.example.com/resource")
.retrieve()
.onStatus(HttpStatus::is4xxClientError, res ->
Mono.error(new RuntimeException("Client error")))
.onStatus(HttpStatus::is5xxServerError, res ->
Mono.error(new RuntimeException("Server error")))
.body(String.class);
Explanation:
- onStatus() handles responses based on HTTP status codes.
- HttpStatus::is4xxClientError handles client-side errors.
- HttpStatus::is5xxServerError handles server-side errors.
4. Request Customization
Developers can add headers, query parameters, and path variables directly through the fluent builder methods.
String response = restClient.get()
.uri("https://api.example.com/users/{id}", 101)
.header("Authorization", "Bearer token123")
.queryParam("active", true)
.retrieve()
.body(String.class);
Explanation:
- header(): adds custom HTTP headers.
- queryParam(): appends query parameters.
- {id}: path variable replaced dynamically.
5. Sending and Receiving Request Bodies
For methods like POST or PUT, RestClient supports serializing Java objects to JSON automatically (using Jackson).
Example – Sending JSON Request:
User user = new User("John", "Doe");
String response = restClient.post()
.uri("https://api.example.com/users")
.body(user)
.retrieve()
.body(String.class);
Example – Receiving as Object:
User responseUser = restClient.get()
.uri("https://api.example.com/users/101")
.retrieve()
.body(User.class);
6. Configuring the Timeout
You can configure connection or read timeouts by customizing the underlying Java HttpClient.
import java.net.http.HttpClient;
import java.time.Duration;
HttpClient httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.build();
RestClient restClient = RestClient.builder()
.httpClient(httpClient)
.build();
This configuration ensures that requests fail gracefully if the connection cannot be established within 5 seconds.
7. Interceptors and Filters
Interceptors can modify requests or responses, ideal for logging or authentication.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;
@Configuration
public class RestClientConfig {
@Bean
public RestClient restClient(RestClient.Builder builder) {
return builder.interceptor((request, next) -> {
System.out.println("Request URI: " + request.getUri());
return next.exchange(request);
}).build();
}
}
This interceptor logs the URI before sending the request.
8. Built-in Support for the HTTP/2
RestClient supports HTTP/2 when used with HttpClient from Java 11+.
HttpClient httpClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.build();
RestClient restClient = RestClient.builder()
.httpClient(httpClient)
.build();
HTTP/2 improves communication speed and efficiency in distributed systems.
Setting up RestClient in Spring Boot
To use RestClient, you need Spring Boot 3.2 or later. Add the following Maven dependency.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Ensure your Spring Boot version is 3.2 or higher, as RestClient is part of Spring Framework 6.1+.
Creating a RestClient Bean
There are two ways to create and configure a RestClient instance in a Spring Boot application.
1. Creating RestClient as a Spring Bean (Recommended)
Define it inside a @Configuration class using RestClientBuilder.
import org.springframework.boot.web.client.RestClientBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;
@Configuration
public class RestClientConfiguration {
@Bean
public RestClient restClient(RestClientBuilder builder) {
return builder.build();
}
}
Explanation:
- This approach allows Spring to manage the RestClient lifecycle.
- It supports dependency injection and centralized configuration.
2. Manually Creating the RestCleint
You can also create a standalone instance without Spring’s dependency injection.
import org.springframework.web.client.RestClient;
public class ApiService {
private final RestClient restClient;
public ApiService() {
this.restClient = RestClient.builder().build();
}
public String getData(String url) {
return restClient.get()
.uri(url)
.retrieve()
.body(String.class);
}
}
Explanation:
- Use this when you need a lightweight or isolated instance.
- Less flexible for configuration sharing across the application.
Using RestClient in a Service Class
Example – GET Request
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;
@Service
public class ApiService {
private final RestClient restClient;
public ApiService(RestClient restClient) {
this.restClient = restClient;
}
public String getApiResponse(String url) {
return restClient.get()
.uri(url)
.retrieve()
.body(String.class);
}
}
Example – POST Request
public String sendPostRequest(String url, Object body) {
return restClient.post()
.uri(url)
.body(body)
.retrieve()
.body(String.class);
}