Why You Should Switch from RestTemplate to Spring’s Official WebClient
The article explains that RestTemplate is deprecated in Spring 5+, outlines WebClient’s non‑blocking I/O, functional style, streaming support and improved error handling, and provides step‑by‑step code examples for synchronous and asynchronous requests, timeout configuration, and error processing.
In Spring Framework 5.0 and later, RestTemplate is deprecated and the officially recommended replacement is WebClient , which is built on Reactor and offers a non‑blocking, reactive approach to I/O, improving scalability and performance for high‑traffic applications.
Non‑blocking I/O : WebClient uses Reactor to handle I/O without blocking threads, enabling better scalability.
Functional style : The API follows a functional programming style, providing a fluent and readable way to configure requests.
Streaming support : It can stream request and response bodies, useful for large files or real‑time data.
Improved error handling : WebClient offers richer error handling and logging, making troubleshooting easier.
Key limitation : Even after upgrading to Spring Web 6.0.0, the request timeout cannot be set via HttpRequestFactory, which is a major reason to abandon RestTemplate.
The article then demonstrates how to use WebClient in a Spring Boot 3 project.
(1) Create the WebClient
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 makes the request synchronous; for asynchronous usage, subscribe() should be considered.
(3) Asynchronous request
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
public static Mono<String> 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);
}Calling the method returns a Mono that can be subscribed to for handling the response or errors.
(4) Handling 4xx and 5xx errors
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
public static Mono<String> 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, clientResponse ->
Mono.error(new RuntimeException("Client error")))
.onStatus(HttpStatus::is5xxServerError, clientResponse ->
Mono.error(new RuntimeException("Server error")))
.bodyToMono(String.class);
}The onStatus callbacks receive a predicate to match status codes and a function that returns a Mono error, which terminates the reactive chain.
(5) Acting on errors in subscribe
makePostRequestAsync("https://example.com/api", "param1=value1¶m2=value2")
.subscribe(response -> {
System.out.println(response);
}, error -> {
System.err.println("An error occurred: " + error.getMessage());
if (error instanceof WebClientResponseException) {
WebClientResponseException ex = (WebClientResponseException) error;
int statusCode = ex.getStatusCode().value();
String statusText = ex.getStatusText();
System.err.println("Error status code: " + statusCode);
System.err.println("Error status text: " + statusText);
}
});The second lambda processes errors; if the error is a WebClientResponseException, the status code and text are extracted and logged.
(6) Full example of handling success and errors
responseMono.subscribe(
response -> {
LOG.info("SUCCESS API Response {}", response);
},
error -> {
LOG.error("An error occurred: {}", error.getMessage());
LOG.error("error class: {}", error.getClass());
if (error instanceof WebClientResponseException) {
WebClientResponseException ex = (WebClientResponseException) error;
int statusCode = ex.getStatusCode().value();
String statusText = ex.getStatusText();
LOG.info("Error status code: {}", statusCode);
LOG.info("Error status text: {}", statusText);
if (statusCode >= 400 && statusCode < 500) {
LOG.info("Error Response body {}", ex.getResponseBodyAsString());
}
Throwable cause = ex.getCause();
if (cause != null) {
LOG.info("Cause {}", cause.getClass());
if (cause instanceof ReadTimeoutException) {
LOG.error("ReadTimeout Exception");
}
if (cause instanceof TimeoutException) {
LOG.error("Timeout Exception");
}
}
}
if (error instanceof WebClientRequestException) {
LOG.error("webClientRequestException");
WebClientRequestException reqEx = (WebClientRequestException) error;
Throwable cause = reqEx.getCause();
if (cause != null) {
LOG.info("Cause {}", cause.getClass());
if (cause instanceof ReadTimeoutException) {
LOG.error("ReadTimeout Exception");
}
if (cause instanceof ConnectTimeoutException) {
LOG.error("Connect Timeout Exception");
}
}
}
});Timeout configuration per request
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 for this request
.block();Connection timeout can only be set once for the client; to use a different connection timeout, a new WebClient instance must be created.
Conclusion
Because RestTemplate is deprecated, developers should adopt WebClient for REST calls; its non‑blocking I/O improves performance, and it also offers enhanced error handling, streaming support, and the ability to operate in a blocking mode to mimic RestTemplate when needed.
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.
IT Niuke
Focused on IT technology sharing, original and innovative content. IT Niuke, we grow together.
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.
