When dealing with synchronous calls in microservice architectures, adjusting the timeout duration for operations is often necessary. OpenFeign, a popular declarative web service client, provides mechanisms to configure these timeouts.
This guide demonstrates how to set up connection and read timeouts for OpenFeign clients within a Spring Cloud environment. We will cover configuration via properties files and Java configuration, illustrate the differences between connection and read timeouts, and discuss the precedence between these configuration methods. Additionally, we'll touch upon related aspects like request/response compression and asynchronous calls.
Environment Setup
The examples are based on the following environment:
- Spring Cloud Version: 2021.0.0
- Spring Boot Starter Parent Version: 2.6.2
- OpenFeign Version: 3.1.0
Configuring Timeouts
OpenFeign offers at least two primary methods for configuring timeouts:
- Via external configuration files (
application.propertiesorapplication.yml). - Via Java configuration classes.
1. Target Service Endpoint
Consider a sample service endpoint that intentionally introduces a delay:
@RequestMapping(value = "/list")
@ResponseBody
public Object list() throws InterruptedException { // Simulate a long-running operation
Thread.sleep(25000); // 25-second delay
Map<String, Object> customerData = new HashMap<>();
customerData.put("name", "John Doe");
customerData.put("sex", "Male");
customerData.put("age", "30");
customerData.put("address", "123 Main St");
return customerData;
}
2. Timeout Configuration via Properties
Timeouts can be defined in your application.yml file. The default configuration applies to all Feign clients unless overridden for a specific client.
feign:
client:
config:
default:
connectTimeout: 30000 # 30 seconds connection timeout
readTimeout: 30000 # 30 seconds read timeout
connectTimeout: The maximum time in milliseconds to establish a connection to the remote service.readTimeout: The maximum time in milliseconds to wait for a response after the connection has been established.
Note: To configure timeouts for a specific Feign client, replace default with the actual service name (e.g., customerService).
3. Timeout Configuration via Java
Alternatively, timeouts can be set programmatically using Java configuration.
Configuration Class:
import feign.codec.ErrorDecoder;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignClientConfig {
@Bean
public Integer connectTimeOut() {
return 30000; // 30 seconds connection timeout
}
@Bean
public Integer readTimeOut() {
return 30000; // 30 seconds read timeout
}
@Bean
public ErrorDecoder feignErrorDecoder() {
// Custom error decoder implementation
return new CustomErrorDecoder();
}
@Bean
public RequestInterceptor customRequestInterceptor() {
return (RequestTemplate template) -> {
String authToken = retrieveAuthToken(); // Method to get auth token
template.header("Authorization", authToken);
};
}
// Placeholder for actual token retrieval logic
private String retrieveAuthToken() {
// Example: return "Bearer " + SecurityContextHolder.getContext().getToken();
return "dummy-token";
}
}
Feign Client Interface:
@FeignClient(name = "customerService", configuration = FeignClientConfig.class)
@Component
public interface CustomerServiceClient {
@RequestMapping(value = "/customer/list", method = RequestMethod.GET)
Object getCustomerList();
}
Note: When both properties and Java configuration are present for the same timeout settings, the properties file configuration takes precedence, and the corresponding Java bean configuration is ignored.
4. Test Controller
A controller to invoke the Feign client:
@RestController
public class TestController {
@Autowired
private CustomerServiceClient customerServiceClient;
@GetMapping(value = "/test-timeout")
@ResponseBody
public ResponseEntity<?> testTimeout() {
long startTime = System.currentTimeMillis();
try {
Object result = customerServiceClient.getCustomerList();
Map<String, Object> responseData = new HashMap<>();
responseData.put("customerData", result);
return ResponseEntity.ok(new ApiResponse(true, "Success", responseData));
} catch (Exception e) {
long endTime = System.currentTimeMillis();
String errorMessage = String.format("Request failed: %s, Duration: %d ms", e.getMessage(), (endTime - startTime));
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new ApiResponse(false, errorMessage, null));
}
}
// Dummy ApiResponse class for demonstration
public static class ApiResponse {
boolean success;
String message;
Object data;
public ApiResponse(boolean success, String message, Object data) {
this.success = success;
this.message = message;
this.data = data;
}
// Getters and setters...
}
}
Testing and Observations
- If only property configuration is set (e.g., 30s timeout), the call will succeed after 25 seconds, returning the data.
- If only Java configuration is set (e.g., 30s timeout), the result is the same as property configuration.
- If both are configured, and the property timeout is set to 5 seconds while Java config is 30 seconds, the property configuration (5s) will win. The client will report an error after approximately 5 seconds.
Example error response when properties win with a 5-second timeout:
{
"success": false,
"message": "Request failed: Read timed out executing GET http://customerService/customer/list, Duration: 5178 ms",
"data": null
}
Related Concerns
Request and Response Compression
For performance optimization or to mitigate timeouts on large paylaods, enabling request and response compression can be beneficial. Configure this in your properties file:
feign:
compression:
request:
enabled: true
min-request-size: 2048 # Minimum size in bytes to trigger compression
response:
enabled: true
Asynchronous Calls
If waiting for a synchronous response is not feasible, consider implementing asynchronous calls. This can be achieved using libraries like AsyncFeign or by leveraging Spring's reactive programming models.
Example using AsyncFeign:
// Configure Async Feign client bean
@Bean
public CustomerServiceClient asyncCustomerServiceClient(SpringEncoder encoder, SpringDecoder decoder) {
return AsyncFeign.asyncBuilder()
.encoder(encoder)
.decoder(decoder)
.target(CustomerServiceClient.class, "http://your-service-url");
}
// Example test method for asynchronous calls
@GetMapping("test-async")
public String testAsyncClient() throws ExecutionException, InterruptedException {
List<CompletableFuture<Object>> futures = new ArrayList<>();
for (int i = 0; i < 5; i++) {
// Assuming CustomerServiceClient has an async method 'getCustomerListAsync()'
// futures.add(asyncCustomerServiceClient.getCustomerListAsync());
}
// Process results from futures
// for (CompletableFuture<Object> future : futures) {
// Object result = future.get();
// log.info("Async Result: " + result);
// }
return "Async calls initiated";
}
Refer to relevant documentation for detailed asynchronous implementation patterns.
Concluding Remarks
OpenFeign provides robust features for managing inter-service communication. While its declarative nature simplifies client implementation, be mindful of configuration precedence and explore options like compression and asynchronous calls to optimize performance and reliability.