How CoApi Integrates with Spring Cloud LoadBalancer for Service Discovery

CoApi integrates with Spring Cloud LoadBalancer by detecting load-balanced declarations through the @LoadBalanced annotation or lb:// URL scheme, then injecting Spring Cloud's LoadBalancerInterceptor or LoadBalancedExchangeFilterFunction into the underlying HTTP client to enable automatic service discovery and request routing.

The ahoo-wang/coapi library provides a declarative HTTP client framework for Spring Boot that abstracts service-to-service communication. By leveraging Spring Cloud LoadBalancer, CoApi automatically routes requests to healthy service instances registered in discovery systems like Eureka, Consul, or Kubernetes without requiring manual load-balancing logic in application code.

Detecting Load-Balanced APIs

CoApi determines whether an interface requires load balancing during the annotation processing phase. Two mechanisms trigger load-balanced mode:

  • @LoadBalanced annotation – A marker annotation applied to the interface
  • lb:// URL scheme – A base URL prefix indicating load-balanced resolution

Both conditions are evaluated in CoApiDefinition.kt:

val resolvedLoadBalanced = getAnnotation(LoadBalanced::class.java) != null
val baseUrlLoadBalanced = resolvedBaseUrl.startsWith(LB_PROTOCOL_PREFIX)
val loadBalanced = resolvedLoadBalanced || baseUrlLoadBalanced

When either condition evaluates to true, the CoApiDefinition.loadBalanced property becomes true and the base URL transforms from lb://service-id to http://service-id for the underlying HTTP client.

Configuring Load-Balanced HTTP Clients

The loadBalanced flag stored in CoApiDefinition drives configuration in the client factory beans. Both synchronous and reactive clients extend AbstractHttpClientFactoryBean to access this flag via the loadBalanced() method.

Synchronous Client Integration

For synchronous HTTP clients using Spring 5's RestClient, RestClientFactoryBean.kt registers a RestClientBuilderCustomizer that conditionally adds the LoadBalancerInterceptor:

if (loadBalanced()) {
    val loadBalancerInterceptor = appContext.getBean(LoadBalancerInterceptor::class.java)
    it.add(loadBalancerInterceptor)
}

The LoadBalancerInterceptor intercepts outgoing requests, extracts the service ID from the URL (e.g., http://todo-service), and delegates to Spring Cloud LoadBalancer to select a concrete instance from the service registry.

Reactive Client Integration

For reactive stacks using Spring WebFlux's WebClient, WebClientFactoryBean.kt injects the reactive LoadBalancedExchangeFilterFunction:

val hasLoadBalancedFilter = it.any { filter -> filter is LoadBalancedExchangeFilterFunction }
if (loadBalanced() && !hasLoadBalancedFilter) {
    appContext.getBean(LoadBalancedExchangeFilterFunction::class.java)
}

This filter function rewrites the request URI and performs the load-balancing lookup for each reactive request stream.

Runtime Service Discovery Flow

When an application sends a request through a CoApi interface, the integration follows this execution path:

  1. Request Interception – The injected LoadBalancerInterceptor (sync) or LoadBalancedExchangeFilterFunction (reactive) intercepts the request containing the logical service ID
  2. Service Resolution – Spring Cloud LoadBalancer queries the configured DiscoveryClient (Eureka, Consul, etc.) to retrieve healthy instances of the target service
  3. Instance Selection – The load balancer applies its selection strategy (round-robin, random, weighted) to choose a specific host and port
  4. Request Routing – The request proceeds to the selected concrete instance

This architecture abstracts the service discovery complexity, allowing developers to work with logical service identifiers while CoApi and Spring Cloud LoadBalancer handle the dynamic resolution.

Implementation Example

The following example demonstrates a complete CoApi setup with Spring Cloud LoadBalancer integration:

// 1. Define the CoApi interface with load balancing
@CoApi(serviceId = "todo-service")
@LoadBalanced
interface TodoApi {
    @GetMapping("/todos/{id}")
    suspend fun findById(@PathVariable id: String): Todo
}

// 2. Enable CoApi in your Spring Boot application
@SpringBootApplication
@EnableCoApi
class DemoApplication

// 3. Inject and use the client
@Service
class TodoService(private val todoApi: TodoApi) {
    suspend fun getTodo(id: String): Todo {
        // Automatically load-balanced across todo-service instances
        return todoApi.findById(id)
    }
}

Alternatively, you can omit @LoadBalanced and specify the lb:// scheme in the @CoApi annotation:

@CoApi(baseUrl = "lb://todo-service")
interface TodoApi {
    @GetMapping("/todos")
    fun listTodos(): List<Todo>
}

Summary

  • Detection mechanism – CoApi identifies load-balanced APIs via the @LoadBalanced annotation or lb:// URL prefix in CoApiDefinition.kt
  • Client configurationRestClientFactoryBean injects LoadBalancerInterceptor for synchronous clients, while WebClientFactoryBean injects LoadBalancedExchangeFilterFunction for reactive clients
  • Automatic resolution – Requests to logical service IDs are automatically resolved to concrete instances via Spring Cloud LoadBalancer
  • Zero boilerplate – Developers declare interfaces with serviceId attributes while CoApi handles the integration with service registries like Eureka or Consul

Frequently Asked Questions

How does CoApi know when to use load balancing without the @LoadBalanced annotation?

CoApi checks for the lb:// protocol prefix in the baseUrl attribute of the @CoApi annotation. In CoApiDefinition.kt, the logic resolvedBaseUrl.startsWith(LB_PROTOCOL_PREFIX) evaluates to true when the URL starts with lb://, automatically enabling load-balanced mode without requiring the explicit annotation marker.

What happens if a service is not registered in the discovery client?

If the service ID specified in the CoApi interface cannot be resolved by the configured DiscoveryClient, Spring Cloud LoadBalancer throws a NoSuchElementException or similar error indicating that no instances are available for the requested service. CoApi propagates this exception to the caller, allowing standard error handling mechanisms to manage service unavailability.

Does CoApi support reactive and synchronous clients equally?

Yes. CoApi provides parallel implementations for both programming models. RestClientFactoryBean handles synchronous HTTP clients using Spring's RestClient, while WebClientFactoryBean manages reactive clients using Spring WebFlux's WebClient. Both factories check the loadBalanced flag and inject the appropriate Spring Cloud LoadBalancer component for their respective execution models.

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 →