Skip to content

When Should I Use RestClient vs WebClient in Spring Boot?

I spent months using WebClient.block() for synchronous HTTP calls. My reasoning? Spring 5 documentation said RestTemplate would be deprecated, and WebClient was the future. So I forced WebClient into everything.

Then Spring 6.1 introduced RestClient. Turns out, I was doing it wrong the whole time.

The Confusion: How We Got Here

The Spring team created this mess unintentionally. In Spring 5.0 (2017), the documentation stated that RestTemplate would be deprecated in a future version. The implication was clear: WebClient is the replacement.

Developers like me took that at face value. We started using WebClient everywhere, even for simple synchronous calls. The .block() pattern became common:

Anti-pattern: WebClient with block()
WebClient webClient = WebClient.create("https://api.example.com");
User user = webClient.get()
.uri("/users/{id}", userId)
.retrieve()
.bodyToMono(User.class)
.block(); // This defeats the purpose of WebClient

This is wrong. WebClient is built for reactive, non-blocking operations. Calling .block() throws away its core benefit.

The Spring team quietly updated the documentation to say RestTemplate is in “maintenance mode” rather than deprecated. But the damage was done. Thousands of codebases had WebClient with .block() scattered throughout.

Spring 6.1’s RestClient is Spring’s admission: the future is not entirely reactive. Sometimes you just need a synchronous HTTP client.

RestClient: The Modern Synchronous Choice

RestClient is what RestTemplate should have been. It has a modern, fluent API designed specifically for synchronous HTTP calls.

When to Use RestClient

  • Traditional servlet-based Spring MVC applications
  • Synchronous, blocking HTTP requests
  • Simple request-response patterns
  • Migrating from RestTemplate
  • Your team lacks reactive programming expertise
  • Java 21+ with virtual threads enabled

RestClient Basic Usage

RestClient basic GET request
RestClient restClient = RestClient.create();
User user = restClient.get()
.uri("https://api.example.com/users/{id}", userId)
.retrieve()
.body(User.class);

RestClient with Configuration

For real applications, you’ll want shared configuration:

RestClient with builder pattern
RestClient restClient = RestClient.builder()
.baseUrl("https://api.example.com")
.defaultHeader("Authorization", "Bearer " + accessToken)
.defaultHeader("Accept", "application/json")
.requestInterceptor(loggingInterceptor)
.build();
// Now all calls use this configuration
User user = restClient.get()
.uri("/users/{id}", userId)
.retrieve()
.body(User.class);

Error Handling

RestClient error handling
import org.springframework.web.client.RestClientResponseException;
try {
User user = restClient.get()
.uri("/users/{id}", userId)
.retrieve()
.body(User.class);
} catch (RestClientResponseException e) {
if (e.getStatusCode().value() == 404) {
throw new UserNotFoundException("User not found: " + userId);
}
throw new ApiException("API call failed: " + e.getMessage());
}

Why RestClient is Better Than RestTemplate

  • Fluent API instead of template method pattern
  • Better integration with modern Spring features
  • Supports multiple HTTP clients: JDK HttpClient, Apache HTTP Components, Jetty, Reactor Netty
  • Cleaner error handling
  • Works naturally with virtual threads (Java 21+)

WebClient: The Reactive Powerhouse

WebClient remains the right choice when you actually need reactive features.

When to Use WebClient

  • Reactive Spring WebFlux applications
  • Asynchronous, non-blocking requirements
  • Streaming large responses
  • High concurrency with limited threads
  • Reactive streams backpressure
  • You’re already invested in the reactive ecosystem

WebClient for Reactive Calls

WebClient reactive example
WebClient webClient = WebClient.create("https://api.example.com");
Mono<User> userMono = webClient.get()
.uri("/users/{id}", userId)
.retrieve()
.bodyToMono(User.class);
// Chain reactive operations
userMono
.flatMap(user -> updateCache(user))
.subscribe(updatedUser -> log.info("Updated: {}", updatedUser));

WebClient for Streaming

This is where WebClient shines - streaming multiple items:

WebClient streaming with Flux
Flux<User> usersFlux = webClient.get()
.uri("/users/stream")
.retrieve()
.bodyToFlux(User.class);
usersFlux
.filter(user -> user.isActive())
.take(100)
.subscribe(user -> processUser(user));

The Key Difference

With RestClient, the call completes and returns the result. With WebClient, you get a Mono or Flux that represents the future result. You chain operations on it, and nothing happens until you subscribe.

If you don’t understand or need that distinction, use RestClient.

The Virtual Threads Factor

Java 21 introduced virtual threads. This changes the calculus significantly.

A Reddit comment captured it well:

“When virtual threads were announced, reactive was doomed.”

Virtual threads make blocking code efficient. Instead of managing complex reactive chains, you can write simple blocking code that performs just as well under high concurrency.

RestClient with virtual threads (Java 21+)
// In application.properties
spring.threads.virtual.enabled=true
// Your RestClient code stays simple and blocking
@RestController
public class UserController {
private final RestClient restClient;
public UserController(RestClient.Builder builder) {
this.restClient = builder
.baseUrl("https://api.example.com")
.build();
}
@GetMapping("/users/{id}")
public User getUser(@PathVariable String id) {
// This runs on a virtual thread - efficient and simple
return restClient.get()
.uri("/users/{id}", id)
.retrieve()
.body(User.class);
}
}

With virtual threads, each request gets its own lightweight thread. Blocking operations don’t tie up platform threads. You get the simplicity of synchronous code with the concurrency benefits that used to require reactive programming.

Decision Checklist

Use this checklist to decide:

RestClient vs WebClient decision matrix
USE RESTCLIENT WHEN:
[x] Building traditional Spring MVC applications
[x] Need synchronous, blocking HTTP calls
[x] Migrating from RestTemplate
[x] Using Java 21+ with virtual threads
[x] Team lacks reactive programming expertise
[x] Simple request-response patterns
[x] Want easier debugging and stack traces
USE WEBCLIENT WHEN:
[x] Building reactive Spring WebFlux applications
[x] Need asynchronous, non-blocking calls
[x] Streaming large datasets
[x] Already invested in reactive ecosystem
[x] Need reactive streams backpressure
[x] High concurrency on limited hardware without virtual threads
AVOID:
[ ] Using WebClient.block() - use RestClient instead
[ ] Mixing reactive and blocking code inconsistently

Migration Examples

From RestTemplate to RestClient

Migration from RestTemplate
// Old: RestTemplate
RestTemplate restTemplate = new RestTemplate();
User user = restTemplate.getForObject(
"https://api.example.com/users/{id}",
User.class,
userId
);
// New: RestClient
RestClient restClient = RestClient.create();
User user = restClient.get()
.uri("https://api.example.com/users/{id}", userId)
.retrieve()
.body(User.class);

From WebClient.block() to RestClient

If your code has WebClient with .block(), migrate to RestClient:

Migration from WebClient.block()
// Anti-pattern
WebClient webClient = WebClient.create();
User user = webClient.get()
.uri("/users/{id}", userId)
.retrieve()
.bodyToMono(User.class)
.block(); // Stop doing this
// Correct approach
RestClient restClient = RestClient.create();
User user = restClient.get()
.uri("/users/{id}", userId)
.retrieve()
.body(User.class);

Common Mistakes

Mistake 1: Using WebClient in Non-Reactive Applications

I did this. It adds complexity without benefit. Your Spring MVC app with WebClient and .block() everywhere is harder to debug and slower than using RestClient directly.

Mistake 2: Believing RestTemplate is Deprecated

It’s in maintenance mode. It still works. But for new code, RestClient is the better choice.

Mistake 3: Mixing Reactive and Blocking Code

Don’t use WebClient in a Spring MVC controller and call .block(). Don’t use RestClient in a WebFlux application. Pick one paradigm and stick with it.

Mistake 4: Ignoring Virtual Threads

If you’re on Java 21+, enable virtual threads. It makes RestClient the clear winner for most use cases.

Best Practices

For RestClient

  1. Use the builder pattern for shared configuration
  2. Set base URLs for microservice communication
  3. Configure timeouts via the underlying HTTP client
  4. Use interceptors for logging, authentication, and cross-cutting concerns
  5. Consider connection pooling for high-volume scenarios
RestClient configuration example
@Configuration
public class RestClientConfig {
@Bean
public RestClient restClient(RestClient.Builder builder) {
HttpClient httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.build();
return builder
.baseUrl("https://api.example.com")
.requestFactory(new JdkClientHttpRequestFactory(httpClient))
.defaultHeader("Accept", "application/json")
.build();
}
}

For WebClient

  1. Stay reactive - never use .block()
  2. Handle errors with onErrorResume
  3. Configure connection pooling
  4. Use Mono for single items, Flux for streams
  5. Test with StepVerifier
WebClient error handling
webClient.get()
.uri("/users/{id}", userId)
.retrieve()
.bodyToMono(User.class)
.onErrorResume(WebClientResponseException.NotFound.class, e ->
Mono.just(new User("not-found")))
.onErrorResume(Exception.class, e ->
Mono.error(new ApiException("Failed to fetch user", e)));

The Bottom Line

RestClient is the modern choice for synchronous HTTP calls. WebClient remains essential for reactive applications. The decision comes down to your architecture:

  • Spring MVC + synchronous needs = RestClient
  • Spring WebFlux + reactive needs = WebClient

And if you’re on Java 21+, virtual threads tip the scales further toward RestClient. You get high-concurrency simplicity without the reactive learning curve.

Stop using WebClient with .block(). Start using RestClient for what it’s designed for.

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:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments