Using Reactive Feign in Spring Cloud Gateway
Problem Root Cause
Spring Cloud Gateway relies on a fully reactive, non-blocking thread pool. Attempting to use standard OpenFeign— which executes synchronous, blocking I/O calls—triggers an IllegalStateException with a message indicating block()/blockFirst()/blockLast() operations are disallowed on reactor-http-nio threads.
Alternative Blocking Load Balancer Override Fix
This approach replaces the default BlockingLoadBalancerClient to shift load-balancing blocking work off the reactive thread pool.
Step 1: Implement Custom Reactive-Friendly Blocking Load Balancer
Create a subclass wrapping reactive load balancer selection logic in a background thread pool exectuor:
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import reactor.core.publisher.Mono;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class OffloadBlockingLoadBalancerClient extends BlockingLoadBalancerClient {
private final ReactiveLoadBalancer.Factory<ServiceInstance> reactiveFactory;
private final ExecutorService backgroundPool;
public OffloadBlockingLoadBalancerClient(LoadBalancerClientFactory factory, LoadBalancerProperties properties) {
super(factory, properties);
this.reactiveFactory = factory;
this.backgroundPool = Executors.newFixedThreadPool(4);
}
@Override
public <T> Response<ServiceInstance> choose(String serviceId, Request<T> request) {
CompletableFuture<Response<ServiceInstance>> future = CompletableFuture.supplyAsync(
() -> Mono.from(reactiveFactory.getInstance(serviceId).choose(request)).block(),
backgroundPool
);
try {
return future.join();
} catch (Exception e) {
return super.choose(serviceId, request);
}
}
}
Step 2: Register Custom Bean
Add this configuration class to your gateway project to override the default client:
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration
public class FeignBlockingWorkaroundConfig {
@Bean
@Primary
public OffloadBlockingLoadBalancerClient offloadLoadBalancerClient(
LoadBalancerClientFactory factory, LoadBalancerProperties properties) {
return new OffloadBlockingLoadBalancerClient(factory, properties);
}
}