New in Spring 6.1: RestClient

Engineering | Arjen Poutsma | July 13, 2023 | ...

Spring Framework 6.1 M2 introduces the RestClient , a new synchronous HTTP client. As the name suggests, RestClient offers the fluent API of WebClient with the infrastructure of RestTemplate .

Fourteen years ago, when RestTemplate was introduced in Spring Framework 3.0, we quickly discovered that exposing every capability of HTTP in a template-like class resulted in too many overloaded methods. In Spring Framework 5, we therefore used a fluent API for the reactive WebClient . With RestClient we are introducing a HTTP client that offers an API similar to WebClient , and that uses the message converters, request factories, interceptors, and other underlying components of RestTemplate .

Creating a RestClient

You can create a RestClient using one of the static create methods. You can also use RestClient::builder to get a builder with further options, such as specifying the HTTP client to use, setting a default URL, path variables, and headers, or registering interceptors and initializers.

Using RestClient::create(RestTemplate) , you can initialize a RestClient with the configuration of an existing RestTemplate .

Retrieve

Let's create a RestClient , use it to set up a basic GET request, and retrieve the contents of a site as string using retrieve :

RestClient restClient = RestClient.create();
String result = restClient.get()
  .uri("https://example.com")
  .retrieve()
  .body(String.class);
System.out.println(result);

If you're interested in the response status code and headers, and not just the contents, you can use toEntity to get a ResponseEntity:

ResponseEntity<String> result = restClient.get()
  .uri("https://example.com")
  .retrieve()
  .toEntity(String.class);
System.out.println("Response status: " + result.getStatusCode());
System.out.println("Response headers: " + result.getHeaders());
System.out.println("Contents: " + result.getBody());

RestClient can also convert JSON to objects, using Jackson under the hood. In fact, it can convert all types that RestTemplate supports, as it uses the same message converters. Note the usage of uri variables, and that the Accept header is set to JSON.

int id = ...
Pet pet = restClient.get()
  .uri("https://petclinic.example.com/pets/{id}", id)
  .accept(APPLICATION_JSON)
  .retrieve()
  .body(Pet.class);

Doing a POST request is just as simple, like so:

Pet pet = ...
ResponseEntity<Void> response = restClient.post()
  .uri("https://petclinic.example.com/pets/new")
  .contentType(APPLICATION_JSON)
  .body(pet)
  .retrieve()
  .toBodilessEntity();

Error handling

By default, RestClient throws a subclass of RestClientException when receiving a 4xx or 5xx status code. This behavior can be overriden using status handlers, like so:

String result = restClient.get()
  .uri("https://example.com/this-url-does-not-exist")
  .retrieve()
  .onStatus(HttpStatusCode::is4xxClientError, (request, response) -> {
      throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders())
  .body(String.class);

Exchange

The RestClient offers the exchange method for more advanced scenarios, as it provides access to the underlying HTTP request and response. The previously mentioned status handlers are not applied when you use exchange, because the exchange function already provides access to the full response, allowing you to perform any error handling necessary:

Pet result = restClient.get()
  .uri("https://petclinic.example.com/pets/{id}", id)
  .accept(APPLICATION_JSON)
  .exchange((request, response) -> {
    if (response.getStatusCode().is4xxClientError()) {
      throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders());
    else {
      Pet pet = convertResponse(response);
      return pet;