In Spring Boot applications, external services often need to be communicated via REST APIs. Traditionally, RestTemplate
was used for this purpose, but it is now considered a legacy approach. Starting from Spring Framework 6.1 and Spring Boot 3.2, RestClient
has been introduced as a modern alternative.
RestClient
provides a fluent and flexible API, supporting synchronous and asynchronous HTTP requests in a Spring Boot application. It focuses on cleaner API design with features such as error handling, customization, and support for modern web standards like HTTP/2.
What is RestClient?
RestClient
is a new API for making HTTP requests in Spring Boot, simplifying HTTP communications with a streamlined, developer-friendly interface. We can easily configure and execute requests using its builder pattern, improving code readability and maintainability.
Key Concepts of RestClient in Spring Boot
1. Fluent API
One of the main features of RestClient
is its fluent API design. This allows developers to build requests in a readable, chainable manner, reducing boilerplate code.
Example:
RestClient restClient = RestClient.builder().build();
String response = restClient.get()
.uri("https://api.example.com/resource")
.retrieve()
.body(String.class);
In this example:
.get()
: Initiates an HTTP GET request..uri()
: Sets the target URI..retrieve()
: Executes the request..body(String.class)
: Extracts the response as a String.
The fluent API makes it easy to understand the sequence of the operations, and the method chaining makes the code cleaner and more expressive.
2. Synchronous and Asynchronous Requests
RestClient
supports both synchronous (blocking) and asynchronous (non-blocking) HTTP calls.
Synchronous Request Example:
String response = restClient.get()
.uri("https://api.example.com/data")
.retrieve()
.body(String.class);
Here, the method waits for the response, blocking the calling thread until it arrives.
Asynchronous Request
For non-blocking scenarios, RestClient returns the CompletableFuture or the reactive type like Mono (if using the Project Reactor) for the handling asynchronous requests.
import java.util.concurrent.CompletableFuture;
CompletableFuture<String> responseFuture = restClient.get()
.uri("https://api.example.com/data")
.retrieve()
.bodyAsync(String.class);
responseFuture.thenAccept(response -> {
System.out.println("Async Response: " + response);
});
In this example,
bodyAsync(String.class)
returns a CompletableFuture<String>
, allowing the application to continue execution without blocking.
3. Error Handling and Status Code Handling
RestClient provides the built-in support for the error handling and HTTP status code management. Developers can easily filter and handle the different HTTP response statuses, ensuring that the errors are handled gracefully.
Basic Error Handling:
RestClient allows us to handle the errors based on the HTTP status codes by using the onStatus() method. It can be useful for catching the specific errors like client-side(4xx) or server-side (5xx) failures.
restClient.get()
.uri("https://api.example.com/resource")
.retrieve()
.onStatus(HttpStatus::is4xxClientError, response -> {
// Handle 4xx errors (client-side errors)
return Mono.error(new RuntimeException("Client error"));
})
.onStatus(HttpStatus::is5xxServerError, response -> {
// Handle 5xx errors (server-side errors)
return Mono.error(new RuntimeException("Server error"));
})
.body(String.class);
Here,
- onStatus(): It can be used to filter responses based on the HTTP status codes.
- HttpStatus::is4xxClientError: It can handle the 4xx errors (e.g., 404 Not Found, 400 Bad Request).
- HttpStatus::is5xxServerError: It can handle the 5xx errors (e.g., 500 Internal Server Error).
Handling the Specific Status Codes:
We can also handle the specific status code to provide the more detailed error messages or responses.
restClient.get()
.uri("https://api.example.com/resource")
.retrieve()
.onStatus(status -> status == HttpStatus.NOT_FOUND, response -> {
return Mono.error(new RuntimeException("Resource not found"));
})
.onStatus(status -> status == HttpStatus.INTERNAL_SERVER_ERROR, response -> {
return Mono.error(new RuntimeException("Internal server error"));
})
.body(String.class);
This approach allows us to create the custom handling logic for the specific status codes, it ensures that the application can respond appropriately to different error scenarios.
4. Request Customization
RestClient provides several ways to customize the HTTP requests, such as adding the headers, query parameters, or path variables. This flexibility allows the developers to meet specific API requirements easily.
Adding Custom Headers:
Header are the crucial part of the HTTP requests, often used for the authentication (eg: JWT tokens) or providing the additional context (eg: content type).
restClient.get()
.uri("https://api.example.com/resource")
.header("Authorization", "Bearer my-token")
.header("Content-Type", "application/json")
.retrieve()
.body(String.class);
In this example,
- header() can be used to add the custom HTTP headers to the request.
- Multiple headers can be added for the different purposes (e.g., authentication and content type).
Path Variables and Query Parameters:
For the RESTful services that requires the path variables or query parameters, RestClient provides the methods to easily inject these values into the URI.
String response = restClient.get()
.uri("https://api.example.com/resource/{id}", 123) // Path variable
.queryParam("filter", "active") // Query parameter
.retrieve()
.body(String.class);
In this example:
- {id} is the path variable, replaced by the value 123.
- queryParam("filter", "active") adds the query parameter (?filter=active) to the request.
5. Body Handling
RestClient provides the methods for sending and receiving the request bodies in different formats (e.g., JSON, XML, String, or custom objects).
Sending Request Body
For the methods like POST, PUT, and PATCH, we can include the request body to be sent to the server.
User user = new User("John", "Doe");
String response = restClient.post()
.uri("https://api.example.com/users")
.body(user) // Send custom object as JSON
.retrieve()
.body(String.class);
Here,
body(user) sends the user object in the request. Spring can automatically serializes the Java object into the JSON using Jackson (assuming the necessary dependencies are included).
Receiving Response Body
We can receive the response body in the various formats, including as the custom object, list, or raw string.
User userResponse = restClient.get()
.uri("https://api.example.com/user/123")
.retrieve()
.body(User.class); // Parse response into custom object
In this case, the response can be de-serialized into the User object.
6. Configuring the Timeout
When making the HTTP requests, we may want to configure the timeouts to avoid waiting indefinitely for the response. It can be done by configuring the underlying HTTP client (HttpClient in Java 11+) used by the RestClient.
import java.time.Duration;
import java.net.http.HttpClient;
HttpClient httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5)) // Set connection timeout
.build();
RestClient restClient = RestClient.builder()
.httpClient(httpClient) // Configure RestClient to use this HttpClient
.build();
Here,
The connectTimeout(Duration.ofSeconds(5)) sets the connection timeout of 5 seconds. If the connection isn’t established within this period, an exception will be thrown.
7. Interceptors and Filters
RestClient supports the interceptors and filter to modify the requests and responses. It can be useful for the tasks like logging, adding the authentication tokens, or performing the request validations.
Example of Adding an Interceptor for Logging:
@Bean
public RestClient restClient(RestClientBuilder builder) {
return builder.interceptor((request, next) -> {
System.out.println("Request URI: " + request.getUri());
return next.exchange(request);
}).build();
}
In this example,
The interceptor logs the request URI before the request is sent. We can also modify the request or response within the interceptor.
8. Built-in Support for the HTTP/2
RestClient supports the modern HTTP standards such as HTTP/2. We can enable this by using the Java's HttpClient, which supports the HTTP/2 out of the box.
HttpClient httpClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2) // Enable HTTP/2
.build();
RestClient restClient = RestClient.builder()
.httpClient(httpClient)
.build();
With HTTP/2, we can take the advantages of faster, more efficient communication between the services, especially in the microservices environments.
Setting up the Project
To use RestClient
, you need Spring Boot 3.2 or later. Add the following Maven dependency.
Maven Dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Make sure the spring-boot-starter-web is compatible with Spring Boot 3.2 or later, as the RestClient API is part of the Spring Framework 6.1.
Creating a RestClient Instance
The RestClient instance can be created in the two ways:
- Through the dependency injection by registering it as the Spring bean.
- By creating it manually in the service or controller class.
Using the Spring Configuration to Create a Bean
The best pratice in the Spring Boot application is to create the RestClient bean that can be reused across the different components.
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 RestClientConfig {
@Bean
public RestClient restClient(RestClientBuilder builder) {
return builder.build(); // Create a RestClient instance
}
}
Here, we have used the RestClientBuilder to build the RestClient instance and register it as the Spring bean. We can inject this bean into any service or controller where you need it.
Manually Creating the RestCleint in a Class
import org.springframework.web.client.RestClient;
public class ApiService {
private final RestClient restClient;
public ApiService() {
this.restClient = RestClient.builder().build(); // Create manually
}
// other methods
}
In this case, RestClient can be manually built using the builder() method. However, using the Spring bean approach is recommended for the better configuration and reuse.
Performing the HTTP requests with RestClient
Now we will explore how to use the RestClient to make the different types of the HTTP requests such as GET, POST, PUT, DELETE etc. We will also request customization, handling the response bodies, and error handling.
Making the simple GET Request
The Get requests is one of the most commonly used the HTTP methods. Here is how to perform the GET request using the RestClient:
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() // Specify that this is a GET request
.uri(url) // Set the URI for the request
.retrieve() // Execute the request and retrieve the response
.body(String.class); // Extract response body as String
}
}
Explanation:
- The restClient.get() method indicates the GET request.
- uri(url) sets the endpoint.
- retrieve() executes the request and retrieves the response.
- body(String.class) parses the response as the String.
Making the Post Request
The post request can be used to send the data to the server to create or update the resources.
Here the example:
import org.springframework.stereotype.Service;
@Service
public class ApiService {
private final RestClient restClient;
public ApiService(RestClient restClient) {
this.restClient = restClient;
}
public String sendPostRequest(String url, Object requestBody) {
return restClient.post() // Specify POST method
.uri(url) // Set the URL
.body(requestBody) // Send the request body
.retrieve() // Execute and get response
.body(String.class); // Extract the response body as String
}
}
In this example,
- We have used the restClient.post() to indicate the POST request.
- The body(requestBody) method sends the request data to the server.
Handling the Response and Errors
Handling Responses:
The RestClient allows us to extract the response bodies in the various formats, such as String, Object, or List.
public MyResponseClass getCustomResponse(String url) {
return restClient.get()
.uri(url)
.retrieve()
.body(MyResponseClass.class); // Parse response into a custom class
}
Here, the response can be parsed into the custom Java object MyResponseClass using the body().
Handling Errors:
We can handle the errors by checking the HTTP status codes using the onStatus() method. It is especially useful for the catching client-side and server-side errors.
import org.springframework.http.HttpStatus;
public String getApiResponseWithErrorHandling(String url) {
return restClient.get()
.uri(url)
.retrieve()
.onStatus(HttpStatus::is4xxClientError, clientResponse ->
Mono.error(new RuntimeException("Client error occurred")))
.onStatus(HttpStatus::is5xxServerError, clientResponse ->
Mono.error(new RuntimeException("Server error occurred")))
.body(String.class);
}
In this example,
- onStatus() checks for the 4xx and 5xx HTTP status codes and handling the errors accordingly.
- If the error occurs, the Mono.error() returned, indicating the error.
Asynchronous Requests with RestClient
RestClient also supports the non-blocking, asynchronous HTTP requests, which can be useful when you don't want to block the application's execution while waiting for the response.
Here’s an example using CompletableFuture for asynchronous execution:
import java.util.concurrent.CompletableFuture;
public CompletableFuture<String> getAsyncResponse(String url) {
return restClient.get()
.uri(url)
.retrieve()
.bodyAsync(String.class); // Fetch response asynchronously
}
This method returns the CompletableFuture<String>, which can be processed asynchronously.
Conclusion
In conclusion, RestClient
offers a modern, flexible, and powerful API for handling HTTP requests in Spring Boot applications. It improves upon RestTemplate
with a fluent API, built-in error handling, support for synchronous and asynchronous requests, and various customization options.
Similar Reads
How to Define a Spring Boot Filter?
A filter in Spring Boot intercepts HTTP requests and responses in the web application. Filters allow developers to perform operations before or after a request is processed by the controller or servlet. They are useful for authentication, logging, request modification, and more tasks. Spring Boot si
7 min read
How to Create a Spring Boot Project?
Spring Boot is built on top of the spring and contains all the features of spring. It is one of the most popular frameworks for building Java-based web applications and microservices. It is a favorite among developers due to its rapid, production-ready environment, which allows developers to focus o
6 min read
How to Make a Simple RestController in Spring Boot?
A RestController in Spring Boot is a specialized controller that is used to develop RESTful web services. It is marked with the @RestController annotation, which combines @Controller and @ResponseBody. This ensures that the response is automatically converted into JSON or XML, eliminating the need f
2 min read
Introduction to Spring Boot
Spring is widely used for creating scalable applications. For web applications, Spring provides Spring MVC, a commonly used module for building robust web applications. The major drawback of traditional Spring projects is that configuration can be time-consuming and overwhelming for new developers.
5 min read
Spring Boot - Admin Client
In Sprint Boot, Admin and Client can be implemented so that the client can be registered with the server, and then the server maintains the client's service health and availability, scales up the service, and also measures the representation of the client. Spring Boot Admin ServerThe Admin Server ca
4 min read
How to Get All Endpoints in Spring Boot?
In Spring Boot applications, listing all the exposed endpoints can be highly beneficial for debugging or documentation purposes. This can be accomplished by leveraging Spring Boot Actuator, a sub-project of Spring Boot that provides production-ready features such as monitoring and management. In thi
3 min read
How to Build Spring Boot Project in VSCode?
Creating a Spring Boot project in Visual Studio Code (VS Code) streamlines the Java development experience. In this article, we'll explore the steps to set up and build a Spring Boot project in VS Code, ensuring a solid foundation to start developing robust applications. Step by Step process to the
3 min read
Spring Boot â Building REST APIs with HATEOAS
In this article, we will explore how to build RESTful APIs using the Spring Boot with HATEOAS (Hypermedia as the Engine of Application State). HATEOAS is the key component of the REST application architecture, where each resource not only provides the data but also includes links to other actions th
5 min read
Spring Boot - Introduction to RESTful Web Services
RESTful Web Services REST stands for REpresentational State Transfer. It was developed by Roy Thomas Fielding, one of the principal authors of the web protocol HTTP. Consequently, REST was an architectural approach designed to make the optimum use of the HTTP protocol. It uses the concepts and verbs
5 min read
Changing Spring Boot Properties at Runtime
Spring Boot provides a flexible way to configure application properties, typically defined statically in the application.properties or application.yml files. However, there are scenarios where properties need to be changed dynamically at runtime without restarting the application. In this article, w
5 min read