Backend Development 11 min read

Migrating from RestTemplate to Spring WebClient: Benefits and Code Samples

Spring developers should replace the deprecated RestTemplate with the reactive WebClient, which offers non‑blocking I/O, a fluent functional API, streaming support, richer error handling and configurable timeouts, while still allowing synchronous calls via block(), as demonstrated by practical Spring Boot 3 code examples.

Java Tech Enthusiast
Java Tech Enthusiast
Java Tech Enthusiast
Migrating from RestTemplate to Spring WebClient: Benefits and Code Samples

In Spring Framework 5.x and later, RestTemplate is deprecated in favor of the newer WebClient . While RestTemplate still works, developers are encouraged to adopt WebClient for new projects.

Key advantages of WebClient over RestTemplate:

Non‑blocking I/O : Built on Reactor, it provides a reactive, non‑blocking approach that improves scalability and performance under high load.

Functional style : Offers a fluent, functional API that is easier to read and customize.

Streaming support : Handles request and response bodies as streams, useful for large files or real‑time data.

Improved error handling : Provides richer error information and logging.

Important: Even after upgrading to Spring Web 6.0.0, setting request timeout in HttpRequestFactory is no longer possible, which is a major reason to abandon RestTemplate.

Below are practical code examples for using WebClient in a Spring Boot 3 application.

(1) Create a WebClient instance

import io.netty.channel.ChannelOption;
import io.netty.channel.ConnectTimeoutException;
import io.netty.handler.timeout.ReadTimeoutException;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.TimeoutException;
import jakarta.annotation.PostConstruct;
import java.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientRequestException;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;

HttpClient httpClient = HttpClient.create()
    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectionTimeout)
    .responseTimeout(Duration.ofMillis(requestTimeout))
    .doOnConnected(conn -> conn.addHandlerLast(new ReadTimeoutHandler(readTimeout)));

WebClient client = WebClient.builder()
    .clientConnector(new ReactorClientHttpConnector(httpClient))
    .build();

(2) Synchronous request (similar to RestTemplate)

public String postSynchronously(String url, String requestBody) {
    LOG.info("Going to hit API - URL {} Body {}", url, requestBody);
    String response = "";
    try {
        response = client.method(HttpMethod.POST)
            .uri(url)
            .accept(MediaType.ALL)
            .contentType(MediaType.APPLICATION_JSON)
            .bodyValue(requestBody)
            .retrieve()
            .bodyToMono(String.class)
            .block();
    } catch (Exception ex) {
        LOG.error("Error while calling API ", ex);
        throw new RuntimeException("XYZ service api error: " + ex.getMessage());
    } finally {
        LOG.info("API Response {}", response);
    }
    return response;
}

The block() call waits synchronously for the response; for fully reactive usage, consider subscribe() instead.

(3) Asynchronous request

public static Mono
makePostRequestAsync(String url, String postData) {
    WebClient webClient = WebClient.builder().build();
    return webClient.post()
        .uri(url)
        .contentType(MediaType.APPLICATION_FORM_URLENCODED)
        .body(BodyInserters.fromFormData("data", postData))
        .retrieve()
        .bodyToMono(String.class);
}

Consume the result with:

makePostRequestAsync("https://example.com/api", "param1=value1&param2=value2")
    .subscribe(response -> System.out.println(response),
               error -> System.err.println(error.getMessage()));

(4) Handling 4xx/5xx errors

public static Mono
makePostRequestAsync(String url, String postData) {
    WebClient webClient = WebClient.builder()
        .baseUrl(url)
        .build();
    return webClient.post()
        .uri("/")
        .contentType(MediaType.APPLICATION_FORM_URLENCODED)
        .body(BodyInserters.fromFormData("data", postData))
        .retrieve()
        .onStatus(HttpStatus::is4xxClientError, resp -> Mono.error(new RuntimeException("Client error")))
        .onStatus(HttpStatus::is5xxServerError, resp -> Mono.error(new RuntimeException("Server error")))
        .bodyToMono(String.class);
}

The onStatus callbacks convert HTTP error statuses into exceptions that propagate downstream.

(5) Full response handling example

responseMono.subscribe(
    response -> LOG.info("SUCCESS API Response {}", response),
    error -> {
        LOG.error("An error occurred: {}", error.getMessage());
        if (error instanceof WebClientResponseException) {
            WebClientResponseException we = (WebClientResponseException) error;
            int status = we.getStatusCode().value();
            LOG.info("Error status code: {}", status);
            LOG.info("Error body: {}", we.getResponseBodyAsString());
        }
        if (error instanceof WebClientRequestException) {
            LOG.error("Request exception", error);
        }
    }
);

(6) Setting timeouts

return webClient.method(this.httpMethod)
    .uri(this.uri)
    .headers(httpHeaders -> httpHeaders.addAll(additionalHeaders))
    .bodyValue(this.requestEntity)
    .retrieve()
    .bodyToMono(responseType)
    .timeout(Duration.ofMillis(readTimeout)) // request timeout per call
    .block();

Connection timeout can only be set once on the underlying HttpClient ; to change it you must create a new WebClient instance.

Conclusion

Since RestTemplate is deprecated, developers should migrate to WebClient for REST calls. Its non‑blocking I/O, enhanced error handling, and streaming capabilities provide better performance and flexibility, while still allowing a blocking style when needed.

JavaSpringHTTPReactiveRestTemplateWebClient
Java Tech Enthusiast
Written by

Java Tech Enthusiast

Sharing computer programming language knowledge, focusing on Java fundamentals, data structures, related tools, Spring Cloud, IntelliJ IDEA... Book giveaways, red‑packet rewards and other perks await!

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.