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

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

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

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

Enable Feign Annotations

@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);
    }
}

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

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);
        // ...
    }
}

Client Implementations

Apache HttpClient

OkHttp

Default (JDK)

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

<!-- 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>

Load‑Balancing Configuration

@Import({
    HttpClientFeignLoadBalancerConfiguration.class,
    OkHttpFeignLoadBalancerConfiguration.class,
    HttpClient5FeignLoadBalancerConfiguration.class,
    DefaultFeignLoadBalancerConfiguration.class })
public class FeignLoadBalancerAutoConfiguration {}

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

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);
    }
}

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

FeignBlockingLoadBalancerClient (Load‑Balancing Implementation)

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());
    }
}

LoadBalancerClient Implementation

public class BlockingLoadBalancerClientAutoConfiguration {
    @Bean
    @ConditionalOnBean(LoadBalancerClientFactory.class)
    @ConditionalOnMissingBean
    public LoadBalancerClient blockingLoadBalancerClient(LoadBalancerClientFactory loadBalancerClientFactory) {
        return new BlockingLoadBalancerClient(loadBalancerClientFactory);
    }
}
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);
    }
}

Round‑Robin Load‑Balancing Algorithm

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);
    }
}

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.

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;
    }
}
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);
    }
}

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

OpenFeignspring-bootspring-cloudload-balancingFeign 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

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.