eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

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.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

Course – LS – NPI (cat=REST)
announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

eBook Jackson – NPI EA – 3 (cat = Jackson)
4 Comments
Oldest
Newest
Inline Feedbacks
View all comments