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
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>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:
RestClient restClient = RestClient.builder()
.baseUrl(properties.getUrl())
.defaultHeader(HttpHeaders.AUTHORIZATION, encodeBasic("pig", "pig"))
.build();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:
String data = restClient.get()
.uri("?name={name}&type={type}", "lengleng", "1")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.body(String.class);
logger.info(data);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:
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());Result Conversion to Bean
RestClient can automatically convert JSON responses to POJOs using the registered Jackson message converters. Example mapping to a record ReqUserResponse:
ReqUserResponse customer = restClient.get()
.uri("/{name}", "lengleng")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.body(ReqUserResponse.class);
logger.info("res name: " + customer.personInfo().name());Fetching a list of customers:
List<ReqUserResponse> customers = restClient.get()
.uri("?type={type}", "1")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.body(List.class);
logger.info("res size " + customers.size());Publishing Data (POST)
To create a new customer, use post() with a JSON body:
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());
}The console shows a 201 CREATED status and the location of the new resource.
Deleting Data
Use delete() to remove a resource:
ResponseEntity<Void> response = restClient.delete()
.uri("/{id}", 2)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.toBodilessEntity();
logger.info("Deleted with status " + response.getStatusCode());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:
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();Alternatively, for a specific delete operation:
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());Exchange Method
The exchange method lets you decode the response differently based on status codes, bypassing status handlers:
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;
}
});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
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
