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.
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><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency></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
urimethod 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
toEntitywhich 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<ReqUserResponse> 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 CREATEDstatus 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_CONTENTstatus.
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
defaultStatusHandleror 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
exchangemethod 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
Java Architecture Diary
Committed to sharing original, high‑quality technical articles; no fluff or promotional content.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.