Cloud Native 12 min read

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.

Coder Trainee
Coder Trainee
Coder Trainee
Simplify Service Calls in Spring Cloud Microservices with OpenFeign (Revised Edition)

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.md

New 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 logs

4. 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:run

2. 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/1

Expected 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: true

Common 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.

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.

microservicesload balancingservice discoverySpring CloudOpenFeigncircuit breakerFeign client
Coder Trainee
Written by

Coder Trainee

Experienced in Java and Python, we share and learn together. For submissions or collaborations, DM us.

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.