Let's get started with a Microservice Architecture with Spring Cloud:
A Guide to RestClient in Spring Boot
Last updated: May 26, 2026
1. Overview
RestClient is a synchronous HTTP client introduced in Spring Framework 6.1 that supersedes RestTemplate. A synchronous HTTP client sends and receives HTTP requests and responses in a blocking manner, meaning it waits for each request to complete before proceeding to the next one.
In this tutorial, we’ll compare RestClient with the now deprecated RestTemplate, demonstrate the usage of RestClient to execute HTTP CRUD requests, and explore the advanced features it offers.
2. From RestTemplate to RestClient
Before looking at how RestClient works, it’s worth understanding why it was introduced and how it fits into the broader Spring HTTP client landscape.
The now-deprecated RestTemplate was built on a template design pattern. It’s a behavioral design pattern that defines the skeleton of an algorithm in a method, so subclasses provide specific implementations for certain steps.
While that worked initially, the RestTemplate class became bloated with multiple overloaded methods for every HTTP verb. That increased the design complexity and affected the everyday programming. For example, autocomplete lists in IDEs got too long, making it difficult to find the correct method signature.
The reactive alternative, WebClient introduced a clean, fluent API that solved the overloading problem. However, it also brought in the full reactive programming model, which was overkill when working with traditional non-reactive applications.
RestClient brings the best of both worlds by offering WebClient’s fluent, readable API while remaining fully synchronous and straightforward to use in standard Spring MVC applications.
3. Dependencies
Before we explore RestClient, let’s add the required dependency to our project’s pom.xml file:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-restclient</artifactId>
<version>4.0.5</version>
</dependency>
Here, we import the spring-boot-starter-restclient dependency, which is a dedicated starter for HTTP client functionality, added in Spring Boot 4.
Alternatively, we can use the RestClient class directly if our project is already using the spring-boot-starter-webmvc or spring-boot-starter-web dependencies.
4. Creating a RestClient Instance
Now, let’s explore some of the ways we can create a RestClient instance. Since we’re using the RestClient starter dependency, we can directly inject the auto-configured builder in our components:
@Autowired
private RestClient.Builder restClientBuilder;
We can then call the build() method on our restClientBuilder field to create a RestClient instance.
Alternatively, if we’re not using the starter, we can create a RestClient instance manually:
RestClient restClient = RestClient.create();
Both of the approaches create a RestClient instance with default settings. However, if we wish to configure some defaults, we can chain configuration methods on the injected builder before calling build(), or use RestClient.builder() instead of RestClient.create(), to set a base URL, default headers, and other configurations that apply to all requests.
Alternatively, if we already have a preconfigured RestTemplate in one of our older codebases, we can simply create a RestClient instance from it:
RestTemplate oldRestTemplate;
RestClient restClient = RestClient.create(oldRestTemplate);
This will create a new RestClient object with all the configurations defined in the old RestTemplate instance.
5. Executing Simple HTTP Request Methods
Similar to RestTemplate, or any other rest client, RestClient allows us to make HTTP calls with request methods. Let’s walk through different HTTP methods to create, retrieve, modify, and delete resources.
We’ll use the Article class:
class Article {
Integer id;
String title;
// standard constructor, getters, and setters
}
For our demonstration, we’ll expose basic CRUD APIs against Article using a simple in-memory implementation. We’ll learn how to use RestClient and use its features by invoking these APIs from our test layer.
However, we should note that in a real-world application, we’ll be using RestClient to invoke external clients typically in our service layer, and use RestTestClient to test these interactions in our integration tests.
5.1. Use POST to Create a Resource
We’ll use the POST HTTP method to submit data to a resource on a web server, often to create new records or resources in web applications. It is designed for sending data to be processed by the server, such as when submitting a web form.
The URI should define what resource we want to process.
Let’s send a simple Article with an ID equal to 1 to our server:
Article article = new Article(1, "How to use RestClient");
ResponseEntity<Void> response = restClient.post()
.uri(uriBase + "/articles")
.contentType(APPLICATION_JSON)
.body(article)
.retrieve()
.toBodilessEntity();
Because we specified the APPLICATION_JSON content type, the instance of the Article class will be automatically serialized to JSON by the Jackson library under the hood. In this example, we ignore the response body using the toBodilessEntity() method. A POST endpoint doesn’t need to, and often doesn’t, return any payload.
5.2. Use GET to Retrieve Resources
Next, we’ll use the GET HTTP method to request and retrieve data from a specified resource on a web server without modifying it. It’s primarily employed for read-only operations in web applications.
To start, let’s get a simple raw String as the response:
String articlesAsString = restClient.get()
.uri(uriBase + "/articles")
.retrieve()
.body(String.class);
assertThat(articlesAsString)
.isEqualToIgnoringWhitespace("""
[{"id":1,"title":"How to use RestClient"}]
""");
Here, we pass String.class to the body() method, instructing RestClient to return the raw response body without any serialization to a custom class. This is particularly useful when we want to inspect the raw JSON output. Later in the tutorial, we’ll look at how to deserialize the response directly into our custom Article class.
5.3. Use PUT to Update a Resource
Next, we’ll look at the PUT HTTP method employed to update or replace an existing resource with the data provided. It’s commonly used for modifying existing entities, or other resources in web applications. Typically, we need to specify the updated resource, ensuring a complete replacement.
Let’s modify the article we created earlier. The URI we provide should identify the resource we want to change:
Article updatedArticle = new Article(id, "How to use RestClient even better");
restClient.put()
.uri(uriBase + "/articles/" + id)
.contentType(MediaType.APPLICATION_JSON)
.body(updatedArticle)
.retrieve()
.toBodilessEntity();
Here again, we rely on RestClient to serialize our payload and ignore the response.
5.4. Use DELETE to Remove a Resource
We’ll use the DELETE HTTP method to request the removal of a specified resource from a web server. Similar to GET endpoints, we usually don’t provide any payload for the request, and rely on parameters encoded in the URI:
restClient.delete()
.uri(uriBase + "/articles/" + id)
.retrieve()
.toBodilessEntity();
6. Configuring API Versioning
When invoking external APIs that support multiple versions, we can use the ApiVersionInserter class to configure the API version in our RestClient instance:
RestClient versionedClient = restClient
.mutate()
.defaultApiVersion("2")
.apiVersionInserter(ApiVersionInserter.useHeader("API-Version"))
.build();
Here, we set 2 as the default API version and instruct RestClient to insert it as an API-Version HTTP header on every request.
In addition to header-based versioning, ApiVersionInserter also supports inserting the version using request path, query parameter, or media type.
7. Support for Request Attributes
Regarding support for request attributes of the type supported in WebClient, it’s important to understand that WebClient introduced attributes to address a limitation, specifically, of reactive environments. This limitation is the lack of dependable thread-locals. Since RestTemplate and RestClient can use thread locals, we rarely need request attributes in this sense.
A few use cases for attributes with a RestClient, such as passing attributes to interceptors, do exist. To facilitate that, Spring Framework 6.2 adds getAttributes to the org.springframework.http.HttpRequest interface. It returns a mutable map of request attributes for an HttpRequest request. We can create an interceptor to update request attributes and attach it to our RestClient instance:
String key = "test-key";
String value = "test-value";
ClientHttpRequestInterceptor interceptor = (request, body, execution) -> {
request.getAttributes().put(key, value);
return execution.execute(request, body);
};
RestClient interceptedClient = restClient
.mutate()
.requestInterceptor(interceptor)
.build();
Under most other circumstances, though, we can build a request URI with query parameters based on a map, without using the request attributes API:
Article fetchedArticle = restClient.get()
.uri(uriBuilder -> uriBuilder
.scheme("http")
.host("localhost")
.port(port)
.path("/articles/search")
.queryParam("title", "RestClient")
.build())
.retrieve()
.body(Article.class);
8. Deserializing Response
We often want to serialize the request and deserialize the response to the class we can efficiently work with. The RestClient is equipped with the ability to perform JSON-to-object conversions, a functionality powered by the Jackson library.
Moreover, we can use all data types supported by RestTemplate because of the shared utilization of message converters.
8.1. Deserializing to a Non-Generic Class
Let’s retrieve an article by its ID, and serialize it to the instance of our custom Article class:
Article fetchedArticle = restClient.get()
.uri(uriBase + "/articles/" + id)
.retrieve()
.body(Article.class);
Here, we pass Article.class to the body() method, and RestClient takes care of the rest, converting the JSON response into a typed object.
8.2. Deserializing to a Generic Class
However, specifying the class of the body is a bit more complicated when we want to get an instance of a generic class like List.
Let’s check an example where we want all the articles to appear as a List<Article> object:
List<Article> articles = restClient.get()
.uri(uriBase + "/articles")
.retrieve()
.body(new ParameterizedTypeReference<>() {});
In this case, we use the ParameterizedTypeReference abstract class to tell RestClient what object we want to get. We don’t have to specify the generic type, as Java can infer the type for us.
8.3. Custom Converters
Additionally, we can attach a custom message converter to our RestClient instance when the built-in converters don’t meet our requirements:
JsonMapper jsonMapper = JsonMapper.builder()
.findAndAddModules()
.enable(SerializationFeature.INDENT_OUTPUT)
.build();
RestClient customClient = restClient
.mutate()
.configureMessageConverters(converters -> converters
.registerDefaults()
.jsonMessageConverter(new JacksonJsonHttpMessageConverter(jsonMapper)))
.build();
Here, we first create a custom JsonMapper with specific features enabled (such as indented output). Then, we create a new RestClient instance using the mutate() method to inherit the existing configuration, and register our custom JacksonJsonHttpMessageConverter by passing our configured JsonMapper to it.
The call to the registerDefaults() method ensures that all other standard converters remain in place alongside our custom one. This approach is particularly useful when we need to apply custom serialization or deserialization rules across all requests made by a specific client.
9. Parsing Response With Exchange
The RestClient includes the exchange() method for handling more advanced situations by granting access to the underlying HTTP request and response. In this approach, the library doesn’t apply default handlers, so we must process the status ourselves.
Let’s say the service returns a 204 status code when no articles are in the database. Because of that slightly nonstandard behavior, we want to handle it in a special way. We’ll throw an ArticleNotFoundException exception when the status code is equal to 204, and also a more generic exception when the status code isn’t equal to 200:
List<Article> article = restClient.get()
.uri(uriBase + "/articles")
.exchange((request, response) -> {
if (response.getStatusCode().isSameCodeAs(HttpStatusCode.valueOf(204))) {
throw new ArticleNotFoundException();
} else if (response.getStatusCode().isSameCodeAs(HttpStatusCode.valueOf(200))) {
return jsonMapper.readValue(response.getBody(), new TypeReference<>() {});
} else {
throw new InvalidArticleResponseException();
}
});
Because we’re working with a raw response here, we also need to deserialize the body of the response ourselves using JsonMapper, or ObjectMapper if working with Jackson 2.
10. Error Handling
By default, when RestClient encounters a 4xx or 5xx status code in the HTTP response, it raises an exception that’s a subclass of RestClientException. We can override this behavior by implementing our own status handler using the onStatus() method.
Let’s write one that will throw a custom exception when we can’t find an article:
Article article = restClient.get()
.uri(uriBase + "/articles/1234")
.retrieve()
.onStatus(status -> status.value() == 404, (request, response) -> {
throw new ArticleNotFoundException(response);
})
.body(Article.class);
In the onStatus() method, we first pass a predicate that receives the HTTP status and returns true when it should be handled. Then, we define a handler function as the second argument. It receives the full request and response objects and throws a custom ArticleNotFoundException.
For handling multiple error statuses, we can chain the onStatus() method. This allows us to intercept specific error codes and throw custom domain exceptions instead of the generic Spring exceptions.
11. Conclusion
In this article, we explored RestClient, the modern synchronous HTTP client that supersedes RestTemplate.
We started by understanding the shortcomings of RestTemplate, and how RestClient effectively bridges the gap between the traditional blocking nature of RestTemplate and the fluent API style of WebClient.
Then, we practically explored creating RestClient instances and executing standard HTTP CRUD requests. Additionally, we covered advanced features of configuring API versioning, working with request attributes, deserializing responses to custom domain objects, and handling exceptions.
The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
















