Simplify Service Calls in Spring Cloud Microservices with OpenFeign (Revised Edition)
This article shows how to replace verbose WebClient/Nacos calls with declarative OpenFeign in a Spring Cloud microservice setup, reducing code by about 80%, improving readability, adding automatic load balancing, retries, logging, and circuit‑breaker support, and provides step‑by‑step configuration, code examples, and common pitfalls.
Goal of This Episode
After using WebClient + Nacos for service discovery and load balancing in the previous episode, the call code became cumbersome. Each remote call required a long chain of WebClient builder calls, leading to duplicated, hard‑to‑maintain code.
This episode introduces OpenFeign to solve the problem.
Why Choose OpenFeign?
Code amount : WebClient/RestTemplate requires many lines; OpenFeign needs only an interface and annotations.
Readability : WebClient URLs are assembled manually and error‑prone; OpenFeign method signatures serve as documentation.
Maintainability : URLs are scattered in code with WebClient; OpenFeign centralizes them in a client interface.
Retry mechanism : Manual with WebClient; automatic with OpenFeign annotations.
Logging : Manual with WebClient; automatic with OpenFeign.
Circuit breaker : Manual; OpenFeign integrates with Sentinel.
Project Structure Changes
spring-cloud-teaching-ep03/ ├── pom.xml (parent) # added Feign dependency
├── docker-compose.yml
├── user-service/ # unchanged from previous episode
│ └── ...
├── order-service/ # main changes here
│ ├── pom.xml # added Feign dependency
│ └── src/main/java/com/teaching/order/
│ ├── OrderServiceApplication.java
│ ├── controller/OrderController.java # inject Feign client
│ └── client/UserFeignClient.java # new Feign interface
└── README.mdNew Configuration Details
1. Parent pom – add OpenFeign dependency management
<dependencyManagement>
<dependencies>
...
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>4.1.0</version>
</dependency>
</dependencies>
</dependencyManagement>2. order-service pom – add Feign and LoadBalancer dependencies
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>3. application.yml – OpenFeign settings
server:
port: 8082
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
# OpenFeign configuration
openfeign:
client:
config:
default:
connectTimeout: 3000 # 3 s
readTimeout: 5000 # 5 s
loggerLevel: full
user-service:
connectTimeout: 2000
readTimeout: 3000
logging:
level:
com.teaching.order.client: DEBUG # print Feign logs4. Feign client interface
package com.teaching.order.client;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.Map;
@FeignClient(name = "user-service")
public interface UserFeignClient {
@GetMapping("/api/user/{id}")
Map<String, Object> getUser(@PathVariable("id") Long id);
}5. Enable Feign in the application
package com.teaching.order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients // enable scanning of Feign clients
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
System.out.println("✅ order-service started!");
}
}6. Refactor OrderController to use Feign
package com.teaching.order.controller;
import com.teaching.order.client.UserFeignClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/order")
public class OrderController {
@Autowired
private UserFeignClient userFeignClient;
@Value("${server.port}")
private String port;
@GetMapping("/{id}")
public Map<String, Object> getOrder(@PathVariable("id") Long id) {
Map<String, Object> order = new HashMap<>();
order.put("orderId", id);
order.put("productName", "Spring Cloud teaching course");
order.put("userId", 1L);
order.put("price", 99.00);
order.put("from", "order-service:" + port);
// Call user-service like a local method
Map<String, Object> userInfo = userFeignClient.getUser(1L);
order.put("userInfo", userInfo);
return order;
}
}Startup Verification
1. Start services
# start Nacos
docker-compose up -d
# start user-service
cd user-service
mvn spring-boot:run
# start order-service
cd order-service
mvn spring-boot:run2. Verify registration in Nacos console
Open http://localhost:8848/nacos. The service list should contain user-service (1 instance) and order-service (1 instance).
3. Test the call
curl http://localhost:8082/api/order/1Expected JSON response:
{
"orderId": 1,
"productName": "Spring Cloud teaching course",
"userId": 1,
"price": 99,
"from": "order-service:8082",
"userInfo": {
"id": 1,
"name": "用户-1",
"email": "[email protected]",
"from": "user-service:8081"
}
}Advanced Features
1. Unified Request Interceptor (pass headers)
package com.teaching.order.config;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import jakarta.servlet.http.HttpServletRequest;
@Configuration
public class FeignConfig {
@Bean
public RequestInterceptor requestInterceptor() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate template) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
String token = request.getHeader("Authorization");
if (token != null) {
template.header("Authorization", token);
}
}
// add common headers
template.header("X-Source", "order-service");
template.header("X-Version", "1.0");
}
};
}
}Update the @FeignClient annotation to use this configuration:
@FeignClient(name = "user-service", configuration = FeignConfig.class)
public interface UserFeignClient { ... }2. Retry Configuration
package com.teaching.order.config;
import feign.Retryer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignRetryConfig {
@Bean
public Retryer feignRetryer() {
// max 3 retries, initial interval 100 ms, max interval 1000 ms
return new Retryer.Default(100, 1000, 3);
}
}3. Fallback (Circuit Breaker) Support
// fallback implementation
@Component
public class UserFeignClientFallback implements UserFeignClient {
@Override
public Map<String, Object> getUser(Long id) {
return Map.of(
"id", id,
"name", "用户服务不可用(降级数据)",
"error", true
);
}
}
// enable fallback in client
@FeignClient(name = "user-service", fallback = UserFeignClientFallback.class)
public interface UserFeignClient { ... }Enable Sentinel circuit‑breaker for Feign:
feign:
circuitbreaker:
enabled: trueCommon Pitfalls
PathVariable must specify the variable name, e.g. @PathVariable("id").
@LoadBalanced is unnecessary for Feign because it already integrates Ribbon/LoadBalancer.
Header loss : Feign does not forward request headers by default; use a RequestInterceptor as shown above.
Code Retrieval
The complete source code can be generated with a one‑click script referenced in the first episode’s GitHub repository.
Relation to Episode 1
Fully independent – can run directly.
Package structure remains com.teaching for consistency.
Code size reduced by about 60% compared with the first episode.
Maintainability greatly improved.
I'm 老 J, see you in the next episode.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Coder Trainee
Experienced in Java and Python, we share and learn together. For submissions or collaborations, DM us.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
