Skip to content
Spring Boot sb microservices 3 min read

Load Balancing

When a service runs several instances, calls must be spread across them. Spring Cloud LoadBalancer does this on the client side: the caller resolves a logical service name against discovery and picks an instance itself — no extra proxy hop. This page wires it into RestClient, RestTemplate, and WebClient, and contrasts it with server-side load balancing.

Client-side load balancing

In client-side load balancing the caller knows the full list of healthy instances (from the registry) and chooses one — by default round-robin.

orders-service                       registry
  │ resolve "inventory-service" ─────────►│
  │◄── [10.0.3.9:8082, 10.0.3.11:8082] ───┘
  │ pick round-robin → 10.0.3.9:8082
  │ next call        → 10.0.3.11:8082

The dependency is usually pulled in transitively by Eureka/Consul clients, but you can add it explicitly:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

Note: Spring Cloud LoadBalancer is the modern replacement for the long-retired Netflix Ribbon. Ribbon is gone in current Spring Cloud — do not look for it.

@LoadBalanced RestClient

RestClient is the Spring Boot 3.2+ synchronous HTTP client. Mark its builder @LoadBalanced and you can target services by name with http://service-name/....

@Configuration
public class HttpClientConfig {

    @Bean
    @LoadBalanced
    RestClient.Builder restClientBuilder() {
        return RestClient.builder();
    }

    @Bean
    RestClient inventoryClient(RestClient.Builder builder) {
        return builder.build();
    }
}
@Service
@RequiredArgsConstructor
public class OrderService {
    private final RestClient inventoryClient;

    public InventoryResponse stock(String sku) {
        return inventoryClient.get()
                .uri("http://inventory-service/inventory/{sku}", sku)  // name, not host
                .retrieve()
                .body(InventoryResponse.class);
    }
}

inventory-service is resolved against the registry and one instance is selected per call.

@LoadBalanced RestTemplate

The same annotation works on a classic RestTemplate:

@Bean
@LoadBalanced
RestTemplate restTemplate(RestTemplateBuilder builder) {
    return builder.build();
}
InventoryResponse r = restTemplate.getForObject(
        "http://inventory-service/inventory/{sku}", InventoryResponse.class, sku);

@LoadBalanced WebClient

For reactive or non-blocking calls, mark the WebClient.Builder:

@Bean
@LoadBalanced
WebClient.Builder webClientBuilder() {
    return WebClient.builder();
}
Mono<InventoryResponse> resp = webClientBuilder.build()
        .get()
        .uri("http://inventory-service/inventory/{sku}", sku)
        .retrieve()
        .bodyToMono(InventoryResponse.class);

See WebClient & RestClient for the full client API.

Choosing the algorithm

The default is round-robin. To switch to random selection (or a custom policy) define a per-service ReactorLoadBalancer configuration:

public class CustomLbConfig {
    @Bean
    ReactorLoadBalancer<ServiceInstance> randomLb(
            Environment env, LoadBalancerClientFactory factory) {
        String name = env.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new RandomLoadBalancer(
                factory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
    }
}
@Configuration
@LoadBalancerClient(name = "inventory-service", configuration = CustomLbConfig.class)
public class LbClients { }

Client-side vs server-side

AspectClient-side (Spring Cloud LoadBalancer)Server-side (proxy / cloud LB)
Decision makerThe calling serviceA dedicated load balancer
Network hopsOne (direct to instance)Two (client → LB → instance)
Needs registryYes (each client queries it)No (LB owns the routing)
Examples@LoadBalanced clients, OpenFeignKubernetes Service, AWS ELB, NGINX
CouplingClients embed LB logicLB is language-agnostic

Tip: On Kubernetes you often don’t need client-side load balancing for in-cluster calls — a Service already load-balances across pods, and you call it by DNS name. Reach for Spring Cloud LoadBalancer when discovery is registry-based (Eureka/Consul) or you want client-side control.

OpenFeign uses Spring Cloud LoadBalancer automatically, so a @FeignClient(name = "inventory-service") is load-balanced with no extra config — see OpenFeign Client.

Last updated June 13, 2026
Was this helpful?