PrepZone Logo
PrepZone

RestClient vs WebClient vs RestTemplate

Three HTTP clients, when to use each, and @HttpExchange declarative clients.

Why this matters

  • RestTemplate is deprecated — it blocks a thread for every outbound call and lacks a fluent API.
  • RestClient (Boot 3.2+) is the modern blocking client: fluent, composable, and works with virtual threads.
  • WebClient is the reactive non-blocking option; use it when the calling code is already on WebFlux or you need streaming responses.
HTTP request
DispatcherServlet
Controller
Service
JSON response
From HTTP arrival to JSON response — know where validation, security and exception handling sit.

Three HTTP clients at a glance

  • RestTemplate — synchronous, blocking, deprecated since Spring 6. Do not start new code with it.
  • RestClient — synchronous, blocking, fluent builder API. Default choice for MVC apps on Boot 3.2+.
  • WebClient — reactive, non-blocking, returns Mono/Flux. Pairs with WebFlux or fire-and-forget async.
  • @HttpExchange — declarative interface backed by RestClient or WebClient via HttpServiceProxyFactory.
  • Observation — all three integrate with Micrometer tracing when observation is enabled.

RestClient — the modern blocking client

Java
@Configuration
public class HttpClientConfig {

    @Bean
    public RestClient inventoryRestClient(RestClient.Builder builder) {
        return builder
                .baseUrl("http://inventory-service:8081")
                .defaultHeader("X-Service-Name", "bookstore-api")
                .build();
    }
}
Java
@Service
public class InventoryClient {

    private final RestClient restClient;

    public InventoryClient(RestClient inventoryRestClient) {
        this.restClient = inventoryRestClient;
    }

    public StockLevel checkStock(String isbn) {
        return restClient.get()
                .uri("/api/stock/{isbn}", isbn)
                .retrieve()
                .onStatus(HttpStatusCode::is4xxClientError, (req, res) -> {
                    throw new InventoryNotFoundException(isbn);
                })
                .body(StockLevel.class);
    }

    public void reserveStock(String isbn, int quantity) {
        restClient.post()
                .uri("/api/stock/{isbn}/reserve", isbn)
                .body(new ReserveRequest(quantity))
                .retrieve()
                .toBodilessEntity();
    }
}

WebClient — reactive outbound calls

Java
@Bean
public WebClient paymentWebClient(WebClient.Builder builder) {
    return builder
            .baseUrl("http://payment-service:8082")
            .filter(ExchangeFilterFunction.ofRequestProcessor(
                    req -> Mono.just(ClientRequest.from(req)
                            .header("X-Service-Name", "bookstore-api")
                            .build())))
            .build();
}

@Service
public class PaymentClient {

    private final WebClient webClient;

    public Mono<PaymentResult> charge(ChargeRequest request) {
        return webClient.post()
                .uri("/api/charges")
                .bodyValue(request)
                .retrieve()
                .bodyToMono(PaymentResult.class)
                .timeout(Duration.ofSeconds(5))
                .retryWhen(Retry.backoff(3, Duration.ofMillis(200)));
    }
}

Declarative clients with @HttpExchange

Java
@HttpExchange("/api/reviews")
public interface ReviewServiceClient {

    @GetExchange("/{isbn}")
    ReviewSummary getReviews(@PathVariable String isbn);

    @PostExchange
    void submitReview(@RequestBody SubmitReviewRequest request);
}
Java
@Configuration
public class DeclarativeClientConfig {

    @Bean
    ReviewServiceClient reviewClient(RestClient.Builder builder) {
        RestClient restClient = builder.baseUrl("http://review-service:8083").build();
        RestClientAdapter adapter = RestClientAdapter.create(restClient);
        HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
        return factory.createClient(ReviewServiceClient.class);
    }
}

Quick recall

Everything you need if you only revisit this box.

  • RestClient is the default blocking HTTP client in Boot 3.2+ — fluent API, replaces RestTemplate.
  • WebClient is for reactive/non-blocking stacks or when composing multiple async upstream calls.
  • @HttpExchange interfaces + HttpServiceProxyFactory provide declarative clients over either backend.
  • Configure timeouts and retries explicitly — defaults will hang or fail silently under upstream outages.
  • Enable observation on clients so outbound calls appear as child spans in distributed traces.

Test yourself

Answer these before moving on — recall is what makes it stick.