Backend Development 11 min read

How Spring Cloud OpenFeign Implements Load Balancing and Client Bean Creation

This article explains how Spring Cloud OpenFeign discovers @FeignClient interfaces, registers them as beans, configures FeignClientFactoryBean, selects the appropriate HTTP client implementation, and integrates load‑balancing through FeignBlockingLoadBalancerClient and related classes.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
How Spring Cloud OpenFeign Implements Load Balancing and Client Bean Creation

Environment

Spring Cloud 2021.0.7 with Spring Boot 2.7.12.

Dependency Configuration

<code>&lt;dependency&gt;
  &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
  &lt;artifactId&gt;spring-cloud-starter-openfeign&lt;/artifactId&gt;
&lt;/dependency&gt;

&lt;dependency&gt;
  &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
  &lt;artifactId&gt;spring-cloud-loadbalancer&lt;/artifactId&gt;
&lt;/dependency&gt;

&lt;dependencyManagement&gt;
  &lt;dependencies&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
      &lt;artifactId&gt;spring-cloud-dependencies&lt;/artifactId&gt;
      &lt;version&gt;${spring-cloud.version}&lt;/version&gt;
      &lt;type&gt;pom&lt;/type&gt;
      &lt;scope&gt;import&lt;/scope&gt;
    &lt;/dependency&gt;
  &lt;/dependencies&gt;
&lt;/dependencyManagement&gt;</code>

Enable Feign Annotations

<code>@SpringBootApplication
// Enable Feign and configure default settings, scan packages, and specify @FeignClient classes
@EnableFeignClients
public class AppApplication {
    public static void main(String[] args) {
        SpringApplication.run(AppApplication.class, args);
    }
}</code>

FeignClient Bean Generation Principle

During container startup, Spring scans for interfaces annotated with @FeignClient and registers each as a bean using a FeignClientFactoryBean instance.

The class FeignClientsRegistrar (imported by @EnableFeignClients ) performs the actual scanning.

FeignClientFactoryBean

<code>public class FeignClientFactoryBean implements FactoryBean<Object> {
    public Object getObject() {
        return getTarget();
    }
    <T> T getTarget() {
        FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class) : applicationContext.getBean(FeignContext.class);
        Feign.Builder builder = feign(context);
        if (!StringUtils.hasText(url)) {
            if (!name.startsWith("http")) {
                url = "http://" + name;
            } else {
                url = name;
            }
            url += cleanPath();
            // load‑balance handling
            return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
        }
        // ...
    }
    protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {
        // Core load‑balancing client implementation
        Client client = getOptional(context, Client.class);
        // ...
    }
}</code>

Client Implementations

Apache HttpClient

OkHttp

Default (JDK)

The actual client used depends on which dependency (httpclient or okhttp) is present on the classpath.

<code><!-- httpclient -->
<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-httpclient</artifactId>
  <version>${version}</version>
</dependency>
<!-- okhttp -->
<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-okhttp</artifactId>
  <version>${version}</version>
</dependency>
</code>

Load‑Balancing Configuration

<code>@Import({
    HttpClientFeignLoadBalancerConfiguration.class,
    OkHttpFeignLoadBalancerConfiguration.class,
    HttpClient5FeignLoadBalancerConfiguration.class,
    DefaultFeignLoadBalancerConfiguration.class })
public class FeignLoadBalancerAutoConfiguration {}
</code>

If multiple implementations are present, the last imported configuration (e.g., DefaultFeignLoadBalancerConfiguration ) takes precedence.

<code>public class DefaultFeignLoadBalancerConfiguration {
    @Bean
    @ConditionalOnMissingBean
    @Conditional(OnRetryNotEnabledCondition.class)
    public Client feignClient(LoadBalancerClient loadBalancerClient, LoadBalancerClientFactory loadBalancerClientFactory) {
        // Construct the final client that performs remote calls
        return new FeignBlockingLoadBalancerClient(new Client.Default(null, null), loadBalancerClient, loadBalancerClientFactory);
    }
}
</code>

When neither httpclient nor okhttp is imported, the client implementation defaults to FeignBlockingLoadBalancerClient .

FeignBlockingLoadBalancerClient (Load‑Balancing Implementation)

<code>public class FeignBlockingLoadBalancerClient implements Client {
    private final Client delegate;
    private final LoadBalancerClient loadBalancerClient;
    private final LoadBalancerClientFactory loadBalancerClientFactory;

    public FeignBlockingLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient, LoadBalancerClientFactory loadBalancerClientFactory) {
        this.delegate = delegate;
        this.loadBalancerClient = loadBalancerClient;
        this.loadBalancerClientFactory = loadBalancerClientFactory;
    }

    @Override
    public Response execute(Request request, Request.Options options) throws IOException {
        URI originalUri = URI.create(request.url());
        String serviceId = originalUri.getHost();
        String hint = getHint(serviceId);
        DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest<>(new RequestDataContext(buildRequestData(request), hint));
        // Choose a service instance via load balancer
        ServiceInstance instance = loadBalancerClient.choose(serviceId, lbRequest);
        // Reconstruct the request URL with the chosen instance
        String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri).toString();
        Request newRequest = buildRequest(request, reconstructedUrl);
        LoadBalancerProperties props = loadBalancerClientFactory.getProperties(serviceId);
        return executeWithLoadBalancerLifecycleProcessing(delegate, options, newRequest, lbRequest, null, null, props.isUseRawStatusCodeInResponseData());
    }

    protected Request buildRequest(Request request, String reconstructedUrl) {
        return Request.create(request.httpMethod(), reconstructedUrl, request.headers(), request.body(), request.charset(), request.requestTemplate());
    }
}
</code>

LoadBalancerClient Implementation

<code>public class BlockingLoadBalancerClientAutoConfiguration {
    @Bean
    @ConditionalOnBean(LoadBalancerClientFactory.class)
    @ConditionalOnMissingBean
    public LoadBalancerClient blockingLoadBalancerClient(LoadBalancerClientFactory loadBalancerClientFactory) {
        return new BlockingLoadBalancerClient(loadBalancerClientFactory);
    }
}
</code>
<code>public class BlockingLoadBalancerClient implements LoadBalancerClient {
    private final ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerClientFactory;

    public BlockingLoadBalancerClient(ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerClientFactory) {
        this.loadBalancerClientFactory = loadBalancerClientFactory;
    }

    public <T> ServiceInstance choose(String serviceId, Request<T> request) {
        ReactiveLoadBalancer<ServiceInstance> loadBalancer = loadBalancerClientFactory.getInstance(serviceId);
        if (loadBalancer == null) {
            return null;
        }
        Response<ServiceInstance> response = Mono.from(loadBalancer.choose(request)).block();
        return response != null ? response.getServer() : null;
    }

    public URI reconstructURI(ServiceInstance serviceInstance, URI original) {
        return LoadBalancerUriTools.reconstructURI(serviceInstance, original);
    }
}
</code>

Round‑Robin Load‑Balancing Algorithm

<code>public class RoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer {
    public Mono<Response<ServiceInstance>> choose(Request request) {
        ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
        return supplier.get(request).next()
            .map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));
    }

    private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) {
        Response<ServiceInstance> response = getInstanceResponse(serviceInstances);
        if (supplier instanceof SelectedInstanceCallback && response.hasServer()) {
            ((SelectedInstanceCallback) supplier).selectedServiceInstance(response.getServer());
        }
        return response;
    }

    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
        if (instances.size() == 1) {
            return new DefaultResponse(instances.get(0));
        }
        int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;
        ServiceInstance instance = instances.get(pos % instances.size());
        return new DefaultResponse(instance);
    }
}
</code>

Remote Call Execution Flow

The FeignBlockingLoadBalancerClient#execute method ultimately delegates to LoadBalancerUtils.executeWithLoadBalancerLifecycleProcessing , which invokes the underlying Client (usually Client.Default ) to perform the HTTP request.

<code>final class LoadBalancerUtils {
    static Response executeWithLoadBalancerLifecycleProcessing(Client feignClient, Request.Options options,
        Request feignRequest, org.springframework.cloud.client.loadbalancer.Request lbRequest,
        org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse,
        Set<LoadBalancerLifecycle> supportedLifecycleProcessors, boolean useRawStatusCodes) throws IOException {
        // Lifecycle pre‑processing can be added here
        Response response = feignClient.execute(feignRequest, options);
        // Lifecycle post‑processing can be added here
        return response;
    }
}
</code>
<code>public interface Client {
    Response execute(Request request, Options options) throws IOException;
}

// Default implementation uses JDK HttpURLConnection
public class Client.Default implements Client {
    public Response execute(Request request, Options options) throws IOException {
        HttpURLConnection connection = convertAndSend(request, options);
        return convertResponse(connection, request);
    }
}
</code>

The article concludes that this chain of components— FeignClientFactoryBean , FeignBlockingLoadBalancerClient , and the various LoadBalancerClient implementations—constitutes the load‑balancing mechanism for a Feign client request in Spring Cloud.

JavaLoad BalancingSpring BootSpring CloudOpenFeignFeign Client
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.