skip to content

When do you back an HTTP Interface with the RestClientAdapter versus the WebClientAdapter, and how does that affect return types?

level: seniorimportance: should knowfreq 40%

answer

  1. RestClient/RestTemplate adapter = blocking
  2. WebClient adapter = reactive, Mono/Flux
  3. servlet app -> RestClientAdapter
  4. return type must match adapter model
  5. don't .block() a WebClient proxy in MVC

basics

~10 s

Use RestClientAdapter for blocking/servlet apps — methods return plain bodies or ResponseEntity. Use WebClientAdapter in reactive apps to additionally return Mono/Flux. RestTemplateAdapter exists for legacy code.

solid answer

~40 s

The adapter you pick decides the concurrency model and legal return types. `RestClientAdapter` (over `RestClient`) and `RestTemplateAdapter` are **blocking/synchronous**: interface methods return the deserialized body (`User`), a collection, `ResponseEntity<T>`, `Optional<T>`, or `void`, and the calling thread waits. `WebClientAdapter` (over `WebClient`) is **reactive/non-blocking**: it supports all those plus `Mono<T>` and `Flux<T>`, so you compose it into a reactive pipeline. In a Spring MVC servlet app you almost always want `RestClientAdapter` — it shares connection pools/converters with your stack and avoids dragging in Reactor. Only use `WebClientAdapter` when you're already reactive (WebFlux) or genuinely need streaming/backpressure. You cannot return `Mono`/`Flux` from a proxy built on the blocking adapters; the adapter type and return type must be consistent.

code

java · 17 lines
java
// Blocking (RestClientAdapter): plain body / ResponseEntity
public interface UserClient {
    @GetExchange("/users/{id}")
    User get(@PathVariable long id);

    @GetExchange("/users/{id}")
    ResponseEntity<User> getWithStatus(@PathVariable long id);
}

// Reactive (WebClientAdapter): Mono / Flux allowed
public interface ReactiveUserClient {
    @GetExchange("/users/{id}")
    Mono<User> get(@PathVariable long id);

    @GetExchange("/users")
    Flux<User> stream();
}

go deeper

for a junior

Know there's a blocking option and a reactive option.

for a middle

Match adapter to stack and know Mono/Flux need WebClient.

for a senior

Explain the HttpExchangeAdapter vs ReactorHttpExchangeAdapter split and the full return-type matrix.

for a principal

Advise on stack-wide client strategy, connection pooling, codecs vs converters, and avoiding accidental blocking on event-loop threads.

## The three adapters | Adapter | Wraps | Model | Extra return types | |---|---|---|---| | `RestClientAdapter` | `RestClient` | Blocking (synchronous) | — | | `RestTemplateAdapter` | `RestTemplate` | Blocking (synchronous) | — | | `WebClientAdapter` | `WebClient` | Reactive (non-blocking) | `Mono<T>`, `Flux<T>` | Internally, `RestClientAdapter` and `RestTemplateAdapter` implement `HttpExchangeAdapter` (blocking), while `WebClientAdapter` implements `ReactorHttpExchangeAdapter` (which extends the blocking one). The factory inspects the adapter's capabilities and the method's declared return type to pick a `HttpServiceMethod` response function. ## Legal return types **All adapters** (blocking or reactive) can return: - the body directly: `User`, `List<User>` - `void` (fire the request, ignore body) - `ResponseEntity<T>` (status + headers + body) - `ResponseEntity<Void>` (status/headers only) - `Optional<T>` - `HttpHeaders` **Only `WebClientAdapter`** additionally allows: - `Mono<T>`, `Mono<Void>`, `Mono<ResponseEntity<T>>` - `Flux<T>` (streamed elements) If you declare a `Mono`/`Flux` return on a proxy built over a blocking adapter, it won't work — the return type must match the adapter's concurrency model. ## Choosing - **Spring MVC / servlet stack** → `RestClientAdapter`. `RestClient` is the modern, fluent, synchronous client (Spring 6.1+) that replaced `RestTemplate` ergonomically while reusing its infrastructure. It keeps you on real, easy-to-debug stack traces and doesn't pull in Reactor. - **Legacy codebase already standardized on `RestTemplate`** → `RestTemplateAdapter`, so you reuse existing interceptors/converters. - **WebFlux / reactive** or need streaming, backpressure, or non-blocking concurrency at scale → `WebClientAdapter`. ## Gotchas - **Don't** use `WebClientAdapter` and then `.block()` everywhere in a servlet app — you get the complexity of Reactor with none of the benefit; prefer `RestClientAdapter`. - Blocking a `WebClient`-backed proxy from a Reactor/Netty event-loop thread can deadlock or throw; the blocking adapters simply call `.block()` for you safely off the event loop only in the right contexts — better to match the adapter to the stack. - The same interface can be reused with different adapters as long as its return types are compatible (i.e., no `Mono`/`Flux` if you might back it with RestClient). - Message converters/codecs differ: RestClient uses `HttpMessageConverter`s; WebClient uses `Codec`s. Custom (de)serialization is configured on the respective client.

  • Can a proxy built over RestClientAdapter return Mono<User>?
    No. Reactive return types (Mono/Flux) require the reactive WebClientAdapter (ReactorHttpExchangeAdapter). A blocking adapter must return synchronous types like the body, Optional, or ResponseEntity.
  • Why prefer RestClientAdapter over WebClientAdapter in a plain Spring MVC service?
    It's synchronous by design (matching the servlet thread-per-request model), gives clean stack traces, reuses HttpMessageConverters, and avoids pulling Reactor/WebFlux in just to call .block() everywhere.

saying these in an interview costs you the question

  • Claiming RestClientAdapter can return Mono/Flux.
  • Defaulting to WebClient in a servlet app and blocking on every call.
  • Thinking RestTemplate is required — RestClient is the modern blocking choice.
  • Assuming ResponseEntity is only available with WebClient.

context