RestClient in Spring Boot – Modern Alternative to RestTemplate

RestClient in Spring Boot – Modern Alternative to RestTemplate

(Deep Dive into Fluent API, Internals & Interceptors)

In the previous part, we understood why RestTemplate is no longer the best choice for modern applications.

Let’s quickly recall its limitations:

  • Too many overloaded methods → hard to remember
  • Designed before Retry / Circuit Breaker concepts
  • Not user friendly for extension
  • In maintenance mode (no new features)

So Spring introduced two modern alternatives:

Client Nature
WebClient Asynchronous / Non-blocking
RestClient Synchronous / Blocking

In this blog, we focus on RestClient.


What is RestClient?

RestClient is a modern, fluent, builder-style HTTP client introduced in:

  • Spring Framework 6.0+
  • Spring Boot 3.0+

It is:

  • Synchronous (blocking)
  • Readable
  • Extensible
  • Future-ready

Architecture Example

We continue with the same setup:

Service Port
OrderService 8081
ProductService 8082

OrderService needs to call ProductService.

Image

Image

Image

Image


What is Fluent API?

Fluent API means method chaining.

Each method:

  • Returns the next stage object
  • Exposes only valid next operations

Example:

Product product = restClient.get()
    .uri("http://localhost:8082/products/1")
    .accept(MediaType.APPLICATION_JSON)
    .retrieve()
    .body(Product.class);

Readable like English.


Core Interfaces in RestClient

Understanding these explains everything:

Entry Points

RequestHeadersUriSpec<?> get();
RequestBodyUriSpec post();
RequestBodyUriSpec put();
RequestHeadersUriSpec<?> delete();

URI Stage

UriSpec<S extends RequestHeadersSpec<?>>
S uri(String uri, Object... vars);

Headers Stage

RequestHeadersSpec<S>
S accept(MediaType...);
S header(String name, String... values);
ResponseSpec retrieve();

Body Stage (POST/PUT)

RequestBodySpec
RequestBodySpec body(Object body);

Response Stage

ResponseSpec
<T> T body(Class<T> type);
<T> ResponseEntity<T> toEntity(Class<T> type);
ResponseEntity<Void> toBodilessEntity();

Why Order of Calls Matters

This fails:

restClient.get()
    .accept(MediaType.APPLICATION_JSON)
    .uri("http://localhost:8082/products/1"); // ❌ Not allowed

Why?

Because:

  • accept() returns RequestHeadersSpec
  • That interface does not contain uri()
  • Generics restrict next method

So sequence matters.


GET Flow Internally

restClient.get()
    .uri(...)
    .accept(...)
    .header(...)
    .retrieve()
    .body(Product.class);

Internally:

  1. Creates DefaultRequestBodyUriSpec
  2. Sets HttpMethod = GET
  3. Sets URI
  4. Adds headers
  5. Creates DefaultResponseSpec
  6. Calls exchange()
  7. Creates TCP connection
  8. Sends HTTP request
  9. Maps response body

POST Flow

restClient.post()
    .uri("/products")
    .body(product)
    .retrieve()
    .toEntity(Product.class);

Now return type is RequestBodySpec, so:

  • You can call body()
  • You can call headers
  • Then retrieve

DELETE Flow

restClient.delete()
    .uri("/products/1")
    .retrieve()
    .toBodilessEntity();

No body expected.


How Response is Handled

Handled by:

DefaultResponseSpec

This class:

  • Knows how to convert body
  • Knows how to map headers
  • Knows how to throw exceptions

Exception Handling

restClient.get()
    .uri("/products/99")
    .retrieve()
    .onStatus(
        status -> status.is4xxClientError(),
        (req, res) -> new RuntimeException("Product not found")
    )
    .body(Product.class);

You define:

  • Which status is error
  • How to handle it

What Happens in exchange()?

This is where real magic happens.

Internally:

JdkClientHttpRequestFactory
        ↓
HttpClient (java.net.http)
        ↓
sendAsync()
        ↓
TCP connection
        ↓
HTTP request
        ↓
HTTP response

Features of new HTTP client:

  • Connection pooling
  • HTTP/1.1 + HTTP/2
  • Async internally
  • Better performance

RestClient vs RestTemplate Internals

Feature RestTemplate RestClient
HTTP client HttpURLConnection java.net.http.HttpClient
Protocol HTTP/1.1 HTTP/1.1 + HTTP/2
Connection pool Basic Advanced
Retry ready No Yes
Circuit breaker No Yes
Interceptors Hard Easy

Adding Interceptors

Interceptors allow:

  • Logging
  • Authentication
  • Tracing
  • Metrics

Example:

RestClient client = RestClient.builder()
    .requestInterceptor((req, body, exec) -> {
        req.getHeaders().add("X-Source", "OrderService");
        return exec.execute(req, body);
    })
    .build();

Now ProductService receives:

X-Source: OrderService

How Interceptor Works Internally

At exchange step:

RestClient.execute()
   ↓
InterceptingClientHttpRequest
   ↓
Your interceptor
   ↓
Real HTTP call

Interceptor wraps the actual execution.


When to Use exchange() Directly?

If you want:

  • Full control over request
  • Custom serialization
  • Custom error handling

Then skip retrieve():

restClient.get()
    .uri("/products/1")
    .exchange((req, res) -> {
        if (res.getStatusCode().is2xxSuccessful()) {
            return new ObjectMapper()
                   .readValue(res.getBody(), Product.class);
        }
        throw new RuntimeException("Error");
    }, true);

RestClient vs WebClient

RestClient WebClient
Blocking Non-blocking
Simpler Reactive
MVC friendly WebFlux
Easier learning Steep learning

Interview Questions

Q1. What is RestClient?

Modern synchronous HTTP client introduced in Spring 6.


Q2. Why RestTemplate is deprecated?

Not extensible, outdated, no support for modern resilience patterns.


Q3. What is Fluent API?

Method chaining where each step exposes only valid next methods.


Q4. Why order of method calls matters?

Because generics restrict which methods are visible.


Q5. Which HTTP client RestClient uses?

java.net.http.HttpClient (JDK 11+).


Q6. How to add interceptors?

Using RestClient.builder().requestInterceptor().


Final Thoughts

RestClient represents Spring’s final evolution of synchronous HTTP:

  • Clean API
  • Strong typing
  • Better internals
  • Resilience friendly

Real-world Recommendation:

Use Case Tool
Legacy RestTemplate
Modern MVC RestClient
Reactive systems WebClient
Large microservices Feign + Resilience4j

If you’re building Spring Boot 3+ applications,
👉 RestClient should be your default choice.

Leave a Reply