Backend Development 9 min read

Master Spring 6.1 RestClient: From Setup to Advanced Error Handling

This guide walks you through Spring 6.1's new RestClient, covering project setup, global client creation, data retrieval, posting, deletion, error handling, and advanced exchange methods with clear code examples for modern Java backend development.

Java Architecture Diary
Java Architecture Diary
Java Architecture Diary
Master Spring 6.1 RestClient: From Setup to Advanced Error Handling

Introduction

Spring Framework offers two HTTP client options: RestTemplate (synchronous, introduced in Spring 3) and WebClient (reactive, part of Spring WebFlux in Spring 5). Spring 6.1 M1 adds RestClient , a new synchronous client that works like WebClient while reusing RestTemplate infrastructure.

Project Setup

Jar Dependency

<code>&lt;dependency&gt;
  &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
  &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
&lt;/dependency&gt;</code>

Creating a Global RestClient

RestClient instances can be created with static methods:

create(): uses the default client.

create(String url): sets a default base URL.

create(RestTemplate restTemplate): builds from an existing RestTemplate.

builder(): returns a builder for custom configuration (headers, error handlers, interceptors, etc.).

builder(RestTemplate restTemplate): builder based on an existing RestTemplate.

Example using the builder:

<code>RestClient restClient = RestClient.builder()
  .baseUrl(properties.getUrl())
  .defaultHeader(HttpHeaders.AUTHORIZATION, encodeBasic("pig", "pig"))
  .build();</code>

baseUrl sets the base URL.

defaultHeader adds a default HTTP request header.

Retrieving Data

Use the client to send HTTP requests and receive responses. Each HTTP method (GET, POST, etc.) has a corresponding method. For a simple GET returning the full response body as a

String

:

<code>String data = restClient.get()
  .uri("?name={name}&type={type}", "lengleng", "1")
  .accept(MediaType.APPLICATION_JSON)
  .retrieve()
  .body(String.class);
logger.info(data);</code>

The

uri

method appends query parameters to the base URL; the first argument is a template, subsequent arguments fill the placeholders. The response is logged as JSON.

To obtain status code and headers, use

toEntity

which returns a

ResponseEntity

:

<code>ResponseEntity response = restClient.get()
  .uri("?name={name}&type={type}", "lengleng", "1")
  .accept(MediaType.APPLICATION_JSON)
  .retrieve()
  .toEntity(String.class);
logger.info("Status " + response.getStatusCode());
logger.info("Headers " + response.getHeaders());</code>

Result Conversion to Bean

RestClient can automatically convert JSON responses to POJOs using the registered Jackson message converters. Example mapping to a record

ReqUserResponse

:

<code>ReqUserResponse customer = restClient.get()
  .uri("/{name}", "lengleng")
  .accept(MediaType.APPLICATION_JSON)
  .retrieve()
  .body(ReqUserResponse.class);
logger.info("res name: " + customer.personInfo().name());</code>

Fetching a list of customers:

<code>List&lt;ReqUserResponse&gt; customers = restClient.get()
  .uri("?type={type}", "1")
  .accept(MediaType.APPLICATION_JSON)
  .retrieve()
  .body(List.class);
logger.info("res size " + customers.size());</code>

Publishing Data (POST)

To create a new customer, use

post()

with a JSON body:

<code>ReqUserResponse customer = new ReqUserResponse(
  "lengleng-plus",
  "1"
);
ResponseEntity<Void> response = restClient.post()
  .accept(MediaType.APPLICATION_JSON)
  .body(customer)
  .retrieve()
  .toBodilessEntity();
if (response.getStatusCode().is2xxSuccessful()) {
  logger.info("Created " + response.getStatusCode());
  logger.info("New URL " + response.getHeaders().getLocation());
}
</code>

The console shows a

201 CREATED

status and the location of the new resource.

Deleting Data

Use

delete()

to remove a resource:

<code>ResponseEntity<Void> response = restClient.delete()
  .uri("/{id}", 2)
  .accept(MediaType.APPLICATION_JSON)
  .retrieve()
  .toBodilessEntity();
logger.info("Deleted with status " + response.getStatusCode());
</code>

Successful deletion returns an empty body and a

204 NO_CONTENT

status.

Handling Errors

When a request results in a 4xx or 5xx status, RestClient throws a subclass of

RestClientException

. You can define custom error handling either globally with

defaultStatusHandler

or per‑request with

onStatus

:

<code>RestClient restClient = RestClient.builder()
  .baseUrl(properties.getUrl())
  .defaultHeader(HttpHeaders.AUTHORIZATION, encodeBasic("pig", "pig"))
  .defaultStatusHandler(
    HttpStatusCode::is4xxClientError,
    (request, response) -> {
      logger.error("Client Error Status " + response.getStatusCode());
      logger.error("Client Error Body " + new String(response.getBody().readAllBytes()));
    })
  .build();
</code>

Alternatively, for a specific delete operation:

<code>ResponseEntity response = restClient.delete()
  .uri("/{id}", 2)
  .accept(MediaType.APPLICATION_JSON)
  .retrieve()
  .onStatus(HttpStatusCode::is4xxClientError,
    (req, res) -> logger.error("Couldn't delete " + res.getStatusText()))
  .toBodilessEntity();
if (response.getStatusCode().is2xxSuccessful())
  logger.info("Deleted with status " + response.getStatusCode());
</code>

Exchange Method

The

exchange

method lets you decode the response differently based on status codes, bypassing status handlers:

<code>SimpleResponse simpleResponse = restClient.get()
  .uri("/{id}", 4)
  .accept(MediaType.APPLICATION_JSON)
  .exchange((req, res) -> {
    switch (res.getStatusCode().value()) {
      case 200 -> SimpleResponse.FOUND;
      case 404 -> SimpleResponse.NOT_FOUND;
      default -> SimpleResponse.ERROR;
    }
  });
</code>

Summary

Compared with the older RestTemplate, the new RestClient API is easier to manage, aligns with the Loom‑based HTTP client standards, integrates smoothly with JDK 21 virtual threads, and delivers higher‑performance HTTP communication for modern Spring applications.

References

[1] Official discussion: https://github.com/spring-projects/spring-framework/issues/29552

backendJavaSpringSpring BootHTTPWebClientRestClient
Java Architecture Diary
Written by

Java Architecture Diary

Committed to sharing original, high‑quality technical articles; no fluff or promotional content.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.