Asynchronous Execution
About
Traditionally, HTTP clients like RestTemplate
or basic Feign clients operate synchronously meaning the thread that initiates a remote call blocks and waits for the response. In high-traffic applications or APIs with long-running calls, this approach leads to thread starvation, reduced throughput, and inefficient resource usage.
OpenFeign addresses this by allowing asynchronous interaction through CompletableFuture
, enabling non-blocking invocation. Instead of halting the current thread, our application can continue processing and receive the response once available.
This approach is not "reactive" in the true sense (like WebClient
with Reactor
) but concurrent and future-based, making it ideal for traditional Spring MVC applications looking to improve concurrency without switching to reactive stacks.
How It Works ?
When we define a method in a Feign interface returning CompletableFuture<T>
:
Feign does not natively support async calls (in older versions), so Spring Cloud Feign (or external libraries like
feign-async
) wrap the call in a task.This task is then submitted to an Executor (typically Spring’s async executor).
The actual HTTP call still uses synchronous IO underneath unless we plug in a truly async HTTP client (like
AsyncHttpClient
).Once the result is available, the future is completed, and downstream consumers can act on it.
Threading and Resource Considerations
We must configure a proper executor using
@EnableAsync
to ensure that threads are efficiently managed.Using
Executors.newFixedThreadPool()
or Spring’sThreadPoolTaskExecutor
allows better control over max concurrent requests, queue size, etc.Overusing async calls without limits can exhaust thread pools always monitor and profile thread usage in production.
OpenFeign Async vs Other Approaches
Return type
CompletableFuture<T>
Mono<T>
/ Flux<T>
T
Blocking behavior
Non-blocking via thread
Truly non-blocking/reactive
Blocking
Reactive Streams support
No
Yes
No
Requires special executor
Yes
No
No
Suitable for MVC apps
Yes
Less Ideal (requires bridge)
Yes
Suitable for reactive apps
No
Yes
No
Example
1. Maven Dependency
Make sure we include Feign and Spring Cloud dependencies in our pom.xml
:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2. Enable Feign and Async Execution
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableFeignClients
@EnableAsync
public class PaymentServiceApplication {
public static void main(String[] args) {
SpringApplication.run(PaymentServiceApplication.class, args);
}
}
3. Async Feign Client Interface
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.concurrent.CompletableFuture;
@FeignClient(name = "user-service", url = "http://localhost:8081")
public interface UserClient {
@GetMapping("/api/users/{id}")
CompletableFuture<UserDTO> getUserById(@PathVariable("id") Long id);
}
4. DTO Class
public class UserDTO {
private Long id;
private String name;
private String email;
// Getters and setters
}
5. Controller Layer
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.CompletableFuture;
@RestController
public class TestController {
private final UserClient userClient;
public TestController(UserClient userClient) {
this.userClient = userClient;
}
@GetMapping("/test/user/{id}")
public CompletableFuture<String> getUserName(@PathVariable Long id) {
return userClient.getUserById(id)
.thenApply(user -> "User name: " + user.getName())
.exceptionally(ex -> "Error: " + ex.getMessage());
}
}
6. Custom Async Executor (Recommended)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@Configuration
public class AsyncConfig implements AsyncConfigurer {
@Bean(name = "asyncExecutor")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(50);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("async-feign-");
executor.initialize();
return executor;
}
}
Last updated