Hook: Interviewers love this question because it checks whether you can spot the difference between waiting for a response and letting the request flow without blocking a thread.
Question: WebClient vs RestTemplate.
Answer: RestTemplate is the classic synchronous HTTP client: the calling thread waits until the remote server replies. WebClient is the newer reactive client: it can send a request without tying up a thread while it waits, which makes it a better fit for high concurrency, streaming, and modern reactive services.
Interview-Ready Answer: “I’d use RestTemplate for simple blocking calls in older or servlet-based code, but for new Spring Boot services I prefer WebClient. The big difference is that RestTemplate blocks a request thread while waiting on the remote service, while WebClient is non-blocking and fits reactive pipelines. In practice, both can call REST endpoints, but WebClient scales better when you have many concurrent calls, streaming, or need to compose async flows cleanly.”
RestTemplate is the older, imperative HTTP client in Spring. It is simple and familiar: call a method, wait, get a response. WebClient is the reactive HTTP client from Spring WebFlux. Reactive means the code is built around asynchronous signals instead of a thread sitting idle; Mono means 0 or 1 result, and Flux means 0 to many results.
RestTemplate, Spring serializes the body through HttpMessageConverters, sends the request through a chosen client request factory, and then blocks the current thread until the response arrives.WebClient, Spring builds a reactive pipeline first and returns a Mono or Flux immediately. The actual network I/O is handled later by a non-blocking runtime such as Reactor Netty, which uses a small set of event-loop threads (threads that keep many sockets moving without parking one thread per request).block(), you force the current thread to wait again, which removes most of the scaling advantage.RestTemplate throws a RestClientResponseException subclass, while WebClient turns that into an error signal unless you customize it with onStatus or exchangeToMono.| Aspect | RestTemplate | WebClient |
|---|---|---|
| Style | Blocking | Non-blocking |
| Return type | Object / ResponseEntity | Mono / Flux |
| Best fit | Servlet apps, legacy code | Reactive apps, high concurrency |
| Streaming | Poor fit | Strong fit |
| Error handling | Exceptions | Error signals |
| Thread usage | One waiting thread per call | Few event-loop threads |
RestTemplate when you have existing MVC code, a small number of outbound calls, and you want the simplest mental model.WebClient when you are building new services, expect many concurrent requests, or need reactive composition, retries, or streaming responses.WebClient in a servlet app too; it does not force your whole application to become reactive.RestTemplate may tie up about 200 request threads during that wait, while WebClient can keep that waiting work off the caller thread.WebClient is not automatically better if you immediately call block() everywhere; that turns it back into a blocking style.block() on a reactive event-loop thread; that can cause starvation and ugly latency spikes.RestTemplate is still supported, but it is not the recommended choice for new reactive systems.Memory check: think “RestTemplate = the thread waits in line; WebClient = take a number and walk away.”
Real-World Story: Imagine a checkout service in an e-commerce app. Every checkout request calls pricing, inventory, and shipping services before charging the card. If the team uses RestTemplate everywhere and one downstream service gets slow, each incoming checkout thread sits blocked. Under load, Tomcat threads fill up, new requests queue, and users see spinning loaders or 502 errors. Logs often show timeouts like Read timed out or connection pool exhaustion, and the blast radius is immediate: carts cannot complete, revenue drops, and support tickets spike. The same system with WebClient can absorb more in-flight calls, but only if the team keeps it non-blocking; if someone adds .block() inside a reactive flow, the app can stall in a different way, with event-loop starvation and very slow responses that are hard to debug.
package com.example.webclientvresttemplate;
import java.time.Duration;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientResponseException;
@SpringBootApplication
public class WebClientVsRestTemplateApplication {
public static void main(String[] args) {
SpringApplication.run(WebClientVsRestTemplateApplication.class, args);
}
@Bean
RestTemplate restTemplate(RestTemplateBuilder builder) {
// Timeouts matter: without them, a slow downstream can hold a thread far longer than you expect.
return builder
.setConnectTimeout(Duration.ofSeconds(2))
.setReadTimeout(Duration.ofSeconds(3))
.build();
}
@Bean
WebClient webClient() {
// This demo calls the local server. In real systems, put the downstream base URL in configuration.
return WebClient.builder()
.baseUrl("http://localhost:8080")
.build();
}
@Bean
ApplicationRunner demoRunner(RestTemplate restTemplate, WebClient webClient) {
return args -> {
System.out.println("=== RestTemplate success ===");
callWithRestTemplate(restTemplate, 1);
System.out.println("=== RestTemplate 404 edge case ===");
callWithRestTemplate(restTemplate, 404);
System.out.println("=== WebClient success ===");
callWithWebClient(webClient, 2);
System.out.println("=== WebClient 404 edge case ===");
callWithWebClient(webClient, 404);
};
}
private void callWithRestTemplate(RestTemplate restTemplate, int id) {
try {
ResponseEntity<UserDto> response = restTemplate.getForEntity(
"http://localhost:8080/api/users/{id}",
UserDto.class,
id);
System.out.println("RestTemplate status=" + response.getStatusCode() + ", body=" + response.getBody());
} catch (HttpClientErrorException.NotFound ex) {
// RestTemplate turns a 404 into an exception, so you must catch it if 404 is an expected case.
System.out.println("RestTemplate got 404 as expected: " + ex.getStatusCode());
}
}
private void callWithWebClient(WebClient webClient, int id) {
try {
UserDto user = webClient.get()
.uri("/api/users/{id}", id)
.retrieve()
.bodyToMono(UserDto.class)
// block() is fine in this tiny demo runner because we are outside a reactive request pipeline.
// In a reactive controller or handler, blocking would waste the non-blocking advantage.
.block(Duration.ofSeconds(2));
System.out.println("WebClient body=" + user);
} catch (WebClientResponseException.NotFound ex) {
// retrieve() treats 4xx/5xx as errors by default, which is usually what you want for API calls.
System.out.println("WebClient got 404 as expected: " + ex.getStatusCode());
}
}
}
@RestController
class UserController {
@GetMapping("/api/users/{id}")
ResponseEntity<UserDto> getUser(@PathVariable int id) {
if (id == 404) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(new UserDto(id, "User-" + id));
}
}
record UserDto(int id, String name) { }
Follow-up & Tricky Questions:
RestTemplate throws exceptions such as HttpClientErrorException and HttpServerErrorException. WebClient gives you a reactive error signal, and you can map status codes with onStatus or exchangeToMono.Flux, and it is one reason reactive systems handle large streams more safely.RestTemplate..block()? You can, but that is only a partial migration. It may compile, but you are still paying the blocking cost, so the real scalability gain is lost.Common Mistakes:
.block() everywhere. That erases most of the reactive benefit. Use reactive chains when you actually want non-blocking behavior.Flux and the endpoint supports streaming.Memory Hook: RestTemplate is the thread waiting in a lobby; WebClient is the thread taking a pager and walking away.
Cheat Sheet:
RestTemplate = blocking, simple, synchronous.WebClient = non-blocking, reactive, scalable.RestTemplate for legacy MVC or small workloads.WebClient for new code, high concurrency, and streaming.Practice Tasks:
RestTemplate call in an existing service with WebClient and keep the same response shape.Flux<UserDto> and observe how streaming differs from a normal single-object response.