WebClient vs RestClient: Which Should I Use for Synchronous Calls in Spring Boot?
I needed to make synchronous HTTP calls in a Spring Boot application, and I kept seeing conflicting advice. Some sources said to use WebClient. Others mentioned RestClient. The Spring documentation itself had changed over time.
Here’s what I found and why RestClient is the right choice for synchronous calls.
The Confusion
When Spring 5.0 came out in 2017, the documentation stated that WebClient would replace RestTemplate. This led to years of blog posts, Stack Overflow answers, and even AI recommendations suggesting WebClient for all HTTP calls.
The problem? WebClient is a reactive client. Using it for synchronous calls requires calling .block() to force blocking behavior on a non-blocking client.
@Servicepublic class UserService {
private final WebClient webClient;
public UserService(WebClient.Builder webClientBuilder) { this.webClient = webClientBuilder .baseUrl("https://api.example.com") .build(); }
public User getUser(Long id) { return webClient.get() .uri("/users/{id}", id) .retrieve() .bodyToMono(User.class) .block(); // Forces synchronous behavior }}This works, but it’s wrong for several reasons:
- Unnecessary dependencies - You need to add
spring-boot-starter-webfluxeven though you’re not using reactive features - Reactive overhead - WebClient creates reactive infrastructure (schedulers, buffers, operators) that never gets used
- Complexity - You’re wrapping your head around Mono/Flux types when you just need a simple HTTP call
The Right Tool: RestClient
Spring 6.1 (released in 2023, included in Spring Boot 3.2) introduced RestClient specifically for synchronous HTTP calls. It has a modern, fluent API similar to WebClient, but it’s designed for the traditional thread-per-request model.
@Servicepublic class UserService {
private final RestClient restClient;
public UserService(RestClient.Builder restClientBuilder) { this.restClient = restClientBuilder .baseUrl("https://api.example.com") .build(); }
public User getUser(Long id) { return restClient.get() .uri("/users/{id}", id) .retrieve() .body(User.class); // Direct return, no blocking }}Same functionality, cleaner code, no reactive baggage.
Comparison
| Aspect | RestClient | WebClient with .block() |
|---|---|---|
| Introduced | Spring 6.1 (2023) | Spring 5.0 (2017) |
| Purpose | Synchronous HTTP | Reactive HTTP |
| Dependencies | spring-web only | Requires spring-webflux |
| Return type | Direct objects | Mono/Flux wrapper |
| API style | Fluent | Fluent |
| Thread model | Thread-per-request | Non-blocking event loop |
| Virtual threads | Works naturally | Not designed for it |
| Learning curve | Simple | Steep (reactive concepts) |
When to Use Each
Use RestClient when:
- Building traditional Spring MVC applications
- Making synchronous HTTP calls
- You don’t need backpressure or streaming
- Using Java 21+ virtual threads for concurrency
Use WebClient when:
- Building Spring WebFlux reactive applications
- Need non-blocking I/O with backpressure
- Streaming large amounts of data
- Your application is already on the reactive stack
@Servicepublic class ReactiveUserService {
private final WebClient webClient;
public ReactiveUserService(WebClient.Builder webClientBuilder) { this.webClient = webClientBuilder .baseUrl("https://api.example.com") .build(); }
// Correct use case: reactive streaming public Flux<User> getAllUsers() { return webClient.get() .uri("/users") .retrieve() .bodyToFlux(User.class); }}What About RestTemplate?
RestTemplate is in maintenance mode since Spring 5.0. It will be officially deprecated in Spring 7.1 (Spring Boot 4.2) and removed in Spring 8 (Spring Boot 5).
If you have existing code using RestTemplate, it still works. For new code, use RestClient.
// Old approach - still works but will be deprecatedPerson person = restTemplate.getForObject("/persons/{id}", Person.class, id);
// New approach with RestClientPerson person = restClient.get() .uri("/persons/{id}", id) .retrieve() .body(Person.class);RestClient Configuration
Spring Boot auto-configures RestClient.Builder as a bean. You can customize it in a configuration class.
@Configurationpublic class RestClientConfig {
@Bean public RestClient restClient(RestClient.Builder builder) { return builder .baseUrl("https://api.example.com") .defaultHeader("Authorization", "Bearer " + apiKey) .requestInterceptor(new LoggingInterceptor()) .build(); }}Error Handling
RestClient throws RestClientException for HTTP errors. You can customize error handling with onStatus.
public User getUser(Long id) { return restClient.get() .uri("/users/{id}", id) .retrieve() .onStatus(status -> status.is4xxClientError(), (request, response) -> { throw new UserNotFoundException("User not found: " + id); }) .body(User.class);}Why the Spring Team Changed Course
The Spring documentation initially said WebClient would replace RestTemplate. This was later changed to “maintenance mode” status. Then RestClient was introduced.
A Reddit commenter put it well: “The introduction of RestClient is Spring putting up the white flag and saying ‘oh actually, the future is not reactive’.”
The virtual threads feature in Java 21 changed the calculus. Before virtual threads, reactive programming was one of the few ways to handle high concurrency with limited threads. Now, you can write simple synchronous code and let virtual threads handle the concurrency.
Bottom Line
For synchronous HTTP calls in Spring Boot, use RestClient. It’s the modern API designed for your use case. WebClient is great for reactive applications, but using .block() to force synchronous behavior adds complexity you don’t need.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
- 👨💻 Spring Framework REST Clients Documentation
- 👨💻 Spring Boot REST Client Guide
- 👨💻 Reddit Discussion: WebClient for Synchronous calls
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments