Why RestTemplate Is Deprecated and How to Migrate to Spring WebClient
This article explains why Spring's RestTemplate has been deprecated, outlines the advantages of the non‑blocking WebClient, and provides detailed migration examples—including GET, POST, error handling, streaming, retries, custom configurations, and best‑practice guidelines for using WebClient in backend development.
For many years, Spring's RestTemplate has been the default choice for client‑side HTTP access, offering a simple synchronous, blocking API. However, the growing demand for non‑blocking, reactive programming—especially in microservice architectures—has exposed RestTemplate's limitations. Since Spring Framework 5, RestTemplate is marked as deprecated, and the Spring team recommends WebClient as its successor.
1. Why RestTemplate Is Deprecated
Blocking nature: RestTemplate is a blocking, synchronous client. The thread that executes a request remains blocked until the operation completes, which can exhaust thread pools and increase latency under heavy load. This model does not scale well for microservices that must handle thousands of concurrent requests.
Limited scalability: The synchronous behavior restricts throughput and latency improvements. Modern systems that require high throughput and low latency favor event‑driven, reactive paradigms, leading to the adoption of non‑blocking APIs such as WebClient.
Lack of reactive support: RestTemplate does not support reactive programming, which is increasingly important in cloud‑native ecosystems. Reactive programming provides better responsiveness, resilience, and elasticity—capabilities that RestTemplate's blocking model cannot deliver.
2. The Rise of WebClient
WebClient is part of the Spring WebFlux library introduced with Spring 5. Compared with RestTemplate, it offers several advantages:
Non‑blocking operations: WebClient runs on Project Reactor's non‑blocking, reactive foundation, allowing it to handle concurrency with fewer threads and lower overhead.
Reactive stack: It integrates with the reactive stack, making it suitable for event‑loop‑based runtimes and high‑concurrency microservice scenarios.
JSON handling and more: WebClient uses Jackson for seamless JSON integration and also supports Server‑Sent Events (SSE), streaming, and other advanced use cases.
3. Migrating from RestTemplate to WebClient
Executing a GET request
RestTemplate:
RestTemplate restTemplate = new RestTemplate();
ResponseEntity
response = restTemplate.getForEntity("http://example.com", String.class);WebClient:
WebClient webClient = WebClient.create();
Mono
response = webClient.get()
.uri("http://example.com")
.retrieve()
.bodyToMono(String.class);
response.subscribe(result -> System.out.println(result));In WebClient the retrieve() method initiates the request, bodyToMono converts the response body to a Mono , and subscribe() processes the result when it becomes available.
Error handling
RestTemplate uses an ErrorHandler interface, requiring separate code blocks. WebClient simplifies error handling with fluent operators.
WebClient example:
WebClient webClient = WebClient.create();
webClient.get()
.uri("http://example.com/some-error-endpoint")
.retrieve()
.onStatus(HttpStatus::isError, response -> {
// handle error status code
return Mono.error(new CustomException("Custom error occurred."));
})
.bodyToMono(String.class);POST request with JSON
RestTemplate:
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity
request = new HttpEntity<>("{\"key\":\"value\"}", headers);
ResponseEntity
response = restTemplate.postForEntity("http://example.com", request, String.class);WebClient:
WebClient webClient = WebClient.create();
Mono
response = webClient.post()
.uri("http://example.com")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue("{\"key\":\"value\"}")
.retrieve()
.bodyToMono(String.class);WebClient makes header and body configuration more concise.
Asynchronous processing
WebClient excels at asynchronous calls, allowing multiple independent API calls to run concurrently.
WebClient webClient = WebClient.create();
Mono
responseOne = webClient.get()
.uri("http://example.com/endpointOne")
.retrieve()
.bodyToMono(String.class);
Mono
responseTwo = webClient.get()
.uri("http://example.com/endpointTwo")
.retrieve()
.bodyToMono(String.class);
Mono.zip(responseOne, responseTwo).subscribe(results -> {
System.out.println("Result 1: " + results.getT1());
System.out.println("Result 2: " + results.getT2());
});Streaming data
WebClient can retrieve response bodies as a data stream, useful for large payloads that should not be fully loaded into memory.
WebClient webClient = WebClient.create();
webClient.get()
.uri("http://example.com/stream")
.accept(MediaType.TEXT_EVENT_STREAM)
.retrieve()
.bodyToFlux(String.class)
.subscribe(data -> System.out.println("Received: " + data));Retry mechanism
WebClient webClient = WebClient.builder()
.baseUrl("http://example.com")
.build();
Mono
response = webClient.get()
.uri("/retry-endpoint")
.retrieve()
.bodyToMono(String.class)
.retryWhen(Retry.backoff(3, Duration.ofSeconds(1))
.onErrorResume(e -> Mono.just("Fallback response"));
response.subscribe(result -> System.out.println(result));Custom client configuration
// Build a WebClient with specific timeouts and default headers
WebClient customWebClient = WebClient.builder()
.baseUrl("http://example.com")
.clientConnector(new ReactorClientHttpConnector(
HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 2000)
.responseTimeout(Duration.ofSeconds(2))
.doOnConnected(conn -> conn
.addHandlerLast(new ReadTimeoutHandler(2))
.addHandlerLast(new WriteTimeoutHandler(2)))))
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader("Another-Header", "Another-Value")
.build();Network client filters
// WebClient with a logging filter
WebClient filteredWebClient = WebClient.builder()
.baseUrl("http://example.com")
.filter((request, next) -> {
System.out.println("Request: " + request.method() + " " + request.url());
return next.exchange(request).doOnSuccessOrError((response, error) -> {
if (response != null) {
System.out.println("Response status: " + response.statusCode());
}
if (error != null) {
System.out.println("Error: " + error.getMessage());
}
});
})
.build();Mutual TLS authentication
// Prepare SSL context with trust store and key store (demo only)
SslContext sslContext = SslContextBuilder.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.keyManager(new File("path/to/client.crt"), new File("path/to/client.key"))
.build();
WebClient secureWebClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(
HttpClient.create().secure(sslContextSpec -> sslContextSpec.sslContext(sslContext))))
.build();4. WebClient Best Practices
Singleton usage
Unlike RestTemplate, which is often instantiated per request, WebClient is designed to be used as a singleton. Define a single WebClient.Builder bean and reuse it throughout the application to avoid unnecessary resource overhead.
@Bean
public WebClient.Builder webClientBuilder() {
return WebClient.builder();
}Error handling
Reactive streams propagate errors downstream until they are handled. Use operators such as onErrorResume , onErrorReturn , and doOnError to manage failures gracefully.
webClient.get()
.uri("/endpoint")
.retrieve()
.bodyToMono(String.class)
.doOnError(e -> log.error("Error occurred", e))
.onErrorResume(e -> Mono.just("Fallback value"));Timeout configuration
Always configure a timeout; otherwise a request may hang indefinitely if the server does not respond.
webClient.get()
.uri("/endpoint")
.retrieve()
.bodyToMono(String.class)
.timeout(Duration.ofSeconds(10));Back‑pressure
When dealing with Flux streams, be aware of back‑pressure and use operators like limitRate to control the flow of data.
Logging
Enable detailed logging by setting the logger for reactor.netty.http.client.HttpClient to DEBUG, which provides request and response details.
Thread context
Reactive pipelines may switch threads multiple times. If you rely on thread‑local variables, consider using libraries such as reactor-context to propagate context across threads.
Avoid blocking calls
The main advantage of WebClient is its non‑blocking nature. If a blocking call is unavoidable, offload it with subscribeOn(Schedulers.boundedElastic()) .
Mono.fromCallable(() -> blockingMethod())
.subscribeOn(Schedulers.boundedElastic());WebClient provides a modern, non‑blocking, reactive way to perform HTTP requests, making it the preferred choice over the deprecated RestTemplate. By following the best‑practice guidelines and understanding reactive patterns, developers can fully leverage WebClient to build efficient, scalable, and responsive backend applications.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
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.