WebClient vs RestClient Backends in CoApi: A Complete Technical Comparison

WebClient provides non-blocking reactive HTTP calls returning Mono or Flux, while RestClient offers synchronous blocking requests returning plain objects, with both backends sharing common configuration patterns through AbstractHttpClientFactoryBean in the CoApi framework.

The ahoo-wang/coapi library abstracts HTTP client creation for Spring applications, offering developers a choice between reactive and synchronous execution models. When defining CoApi interfaces, selecting between WebClient and RestClient backends determines your application's concurrency characteristics, return types, and load-balancing mechanisms. Understanding these distinctions ensures optimal performance for both high-throughput reactive services and traditional blocking architectures.

Core Architecture and Factory Beans

WebClient Factory Structure

In spring/src/main/kotlin/me/ahoo/coapi/spring/client/reactive/WebClientFactoryBean.kt, CoApi creates org.springframework.web.reactive.function.client.WebClient instances for reactive workloads. This factory extends AbstractWebClientFactoryBean.kt, which manages base URL resolution from CoApiDefinition and applies WebClientBuilderCustomizer configurations.

The factory automatically detects load-balancing requirements and, when necessary, utilizes LoadBalancedWebClientBuilderCustomizer to inject LoadBalancedExchangeFilterFunction into the builder chain.

RestClient Factory Structure

Conversely, spring/src/main/kotlin/me/ahoo/coapi/spring/client/sync/RestClientFactoryBean.kt instantiates org.springframework.web.client.RestClient for blocking operations, extending AbstractRestClientFactoryBean.kt. Both factories ultimately inherit from AbstractHttpClientFactoryBean, ensuring consistent property handling while maintaining separate customization hooks for their respective Spring client types.

When load balancing is enabled, this factory employs LoadBalancedRestClientBuilderCustomizer to configure LoadBalancerInterceptor appropriately.

Execution Models and Return Types

The fundamental distinction lies in the response handling and threading models:

WebClient (Reactive): Returns Mono or Flux types, enabling non-blocking I/O with back-pressure support. The underlying Netty event loop handles concurrent requests without blocking threads, making it ideal for high-throughput scenarios.

RestClient (Synchronous): Returns plain objects (e.g., String, List<T>, or custom DTOs). Each request blocks the calling thread until the HTTP response completes, simplifying imperative programming models but consuming thread resources per request.

Customization Hooks and Load Balancing

Both backends support Spring Cloud load balancing but implement cross-cutting concerns through different interceptor patterns:

WebClient Customization:

  • Uses WebClientBuilderCustomizer to inject ExchangeFilterFunction instances
  • Load balancing leverages LoadBalancedExchangeFilterFunction (reactive filter)
  • Configuration via ClientProperties.filter list processed in AbstractWebClientFactoryBean.kt

RestClient Customization:

  • Uses RestClientBuilderCustomizer to inject ClientHttpRequestInterceptor instances
  • Load balancing leverages LoadBalancerInterceptor (blocking interceptor)
  • Configuration via ClientProperties.interceptor list processed in AbstractRestClientFactoryBean.kt

When loadBalanced() is enabled or a serviceId is specified, the respective factory automatically selects the appropriate load-balancing customizer before applying any globally registered customizer beans in order.

Practical Configuration Examples

Reactive API with WebClient

Define an interface returning reactive types to leverage the WebClient backend:

@CoApi(baseUrl = "https://api.github.com")
interface GitHubReactiveApi {
    @GetExchange("repos/{owner}/{repo}/issues")
    fun listIssues(@PathVariable owner: String,
                   @PathVariable repo: String): Flux<Issue>
}

The method returns a Flux<Issue>—the call executes on the non-blocking Netty event loop.

Synchronous API with RestClient

Define an interface returning concrete types to use the RestClient backend:

@CoApi(baseUrl = "https://api.github.com")
interface GitHubSyncApi {
    @GetExchange("repos/{owner}/{repo}/issues")
    fun listIssues(@PathVariable owner: String,
                   @PathVariable repo: String): List<Issue>
}

The method returns a plain List<Issue>—the RestClient performs a blocking HTTP request.

Enabling Load Balancing

Both backends support identical service discovery syntax:

@CoApi(serviceId = "github-service")
interface LoadBalancedApi {
    @GetExchange("/endpoint")
    fun fetchData(): String
}

The factories automatically detect load-balancing requirements and inject LoadBalancedExchangeFilterFunction for WebClient or LoadBalancerInterceptor for RestClient accordingly.

Summary

  • WebClient backends in CoApi provide non-blocking reactive HTTP via WebClientFactoryBean, returning Mono/Flux and using ExchangeFilterFunction for cross-cutting concerns.
  • RestClient backends deliver synchronous blocking HTTP via RestClientFactoryBean, returning plain objects and using ClientHttpRequestInterceptor for request modification.
  • Both architectures share common base configuration through AbstractHttpClientFactoryBean while maintaining distinct customization patterns for filters versus interceptors.
  • Load balancing works identically from the consumer perspective but uses reactive filters for WebClient and blocking interceptors for RestClient under the hood.

Frequently Asked Questions

Can I use both WebClient and RestClient backends in the same CoApi application?

Yes. CoApi supports mixing reactive and synchronous clients within the same Spring application. Define separate interfaces annotated with @CoApi—returning Mono or Flux for WebClient usage and plain types for RestClient. The framework instantiates the appropriate factory bean based on your method return types and classpath dependencies.

How does load balancing configuration differ between the two backends?

From the developer perspective, configuration is identical: use serviceId or lb:// URLs in your @CoApi annotation. Internally, WebClientFactoryBean injects LoadBalancedExchangeFilterFunction (a reactive filter), while RestClientFactoryBean injects LoadBalancerInterceptor (a blocking interceptor). Both are configured automatically when load balancing is detected.

Which backend should I choose for high-concurrency microservices?

Choose WebClient for high-concurrency scenarios requiring efficient resource utilization. Its non-blocking nature allows handling thousands of concurrent connections with fewer threads, leveraging back-pressure through Flux and Mono return types. RestClient suits simpler workloads or when integrating with legacy blocking code where thread-per-request semantics are acceptable.

Where are the factory implementations located in the CoApi source code?

The reactive stack resides in spring/src/main/kotlin/me/ahoo/coapi/spring/client/reactive/, containing WebClientFactoryBean.kt and AbstractWebClientFactoryBean.kt. The synchronous stack lives in spring/src/main/kotlin/me/ahoo/coapi/spring/client/sync/, containing RestClientFactoryBean.kt and AbstractRestClientFactoryBean.kt.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →