Backend Development 10 min read

Master Spring 6.1 RestClient: Build, Retrieve, and Manage HTTP Calls

This guide explains how to use Spring 6.1's new RestClient for synchronous HTTP calls, covering setup, request building, response handling, data conversion, error processing, and advanced exchange methods with full code examples.

macrozheng
macrozheng
macrozheng
Master Spring 6.1 RestClient: Build, Retrieve, and Manage HTTP Calls

Introduction

Spring provides two HTTP clients: RestTemplate (synchronous) and WebClient (reactive). Spring 6.1 M1 adds RestClient, a new synchronous client that shares the same infrastructure as WebClient.

Project Setup

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>

Create a Global RestClient

RestClient instances can be created with static methods such as

create()

,

create(String url)

,

create(RestTemplate)

,

builder()

, and

builder(RestTemplate)

.

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

Retrieve Data

Use the fluent API to send a GET request, specify query parameters, accept JSON, and retrieve the response body as a string or a typed object.

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

To obtain a

ResponseEntity

with status and headers, call

toEntity

instead of

body

.

<code>ResponseEntity<String> 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>

Convert Response to Bean

Spring automatically registers a Jackson message converter, allowing the response body to be mapped to a POJO.

<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 works similarly.

<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>

Publish Data (POST)

Use

post()

to send a new resource. The example creates a customer and checks the response status and location header.

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

Delete Data

Calling

delete()

issues an HTTP DELETE request. When successful, the body is empty.

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

Handle Errors

Client (4xx) or server (5xx) errors throw a

RestClientException

. You can register a default status handler or use

onStatus

for per‑request handling.

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

Alternatively, use

onStatus

on a specific request.

<code>ResponseEntity<Void> 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>

Conclusion

Compared with the older RestTemplate, the new RestClient API is easier to manage, aligns with the Loom‑based HTTP client standards, works well with JDK 21 virtual threads, and delivers higher performance for backend Java applications.

BackendJavaSpringhttpSpringBootRestClient
macrozheng
Written by

macrozheng

Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.

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.