# How CoApi Integrates with Spring Cloud LoadBalancer for Service Discovery

> Learn how CoApi integrates with Spring Cloud LoadBalancer for seamless service discovery. Understand how it automatically routes requests using @LoadBalanced annotations and lb:// URLs.

- Repository: [Ahoo Wang/coapi](https://github.com/ahoo-wang/coapi)
- Tags: how-to-guide
- Published: 2026-02-23

---

**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`](https://github.com/ahoo-wang/coapi/blob/main/CoApiDefinition.kt)**:

```kotlin
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`](https://github.com/ahoo-wang/coapi/blob/main/RestClientFactoryBean.kt)** registers a `RestClientBuilderCustomizer` that conditionally adds the `LoadBalancerInterceptor`:

```kotlin
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`](https://github.com/ahoo-wang/coapi/blob/main/WebClientFactoryBean.kt)** injects the reactive `LoadBalancedExchangeFilterFunction`:

```kotlin
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:

```kotlin
// 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:

```kotlin
@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`](https://github.com/ahoo-wang/coapi/blob/main/CoApiDefinition.kt)
- **Client configuration** – `RestClientFactoryBean` 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`](https://github.com/ahoo-wang/coapi/blob/main/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.