Feign Remote Calls

📢 This article was translated by gemini-3-flash-preview

Feign is a declarative HTTP client. GitHub: https://github.com/OpenFeign/feign

Simple Usage

Add the dependency:

1
2
3
4
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

Add the annotation to the startup class to enable Feign:

1
2
@EnableFeignClients
@SpringBootApplication

Create a Feign client:

1
2
3
4
5
@FeignClient("userService")
public interface UserClient {
    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
}

Replace RestTemplate:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;
    // @Autowired
    // private RestTemplate restTemplate;
    @Autowired
    private UserClient userClient;

    public Order queryOrderById(Long orderId) {
        // 1. Query order
        Order order = orderMapper.findById(orderId);
        // 2. Query user
        // String url = "http://localhost:8081/user/" + order.getUserId();
//        String url = "http://userService/user/" + order.getUserId();
//        User user = restTemplate.getForObject(url, User.class);
        // Feign
        User user = userClient.findById(order.getUserId());
        // 3. Populate user info
        order.setUser(user);
        // 4. Return
        return order;
    }
}

Custom Configuration

Feign allows custom configurations to override defaults. Key configurations include:

TypeFunctionDescription
feign.Logger.LevelModify log levelFour levels: NONE, BASIC, HEADERS, FULL
feign.codec.DecoderResponse decoderParses HTTP call results, e.g., JSON string to Java object
feign.codec.EncoderRequest encoderEncodes request parameters for HTTP transmission
feign. ContractAnnotation formatDefaults to SpringMVC annotations
feign. RetryerRetry mechanismRetry logic for failed requests; default is none, but uses Ribbon’s retry

Usually, you only need to configure the log level. Levels include:

  • NONE: No logging (default).
  • BASIC: Logs only the request method, URL, response status code, and execution time.
  • HEADERS: BASIC plus request and response headers.
  • FULL: Logs everything—headers, body, and metadata for both request and response.

There are two ways to configure this: via configuration files or by creating a Bean.

Configuration File

For a specific service:

1
2
3
4
5
feign:  
  client:
    config: 
      userservice: # Target microservice
        loggerLevel: FULL # Log level 

Global configuration for all services:

1
2
3
4
5
feign:  
  client:
    config: 
      default: # 'default' applies globally
        loggerLevel: FULL # Log level 

Bean Configuration

First, define a configuration class and declare a Logger.Level Bean:

1
2
3
4
5
6
public class DefaultFeignConfiguration  {
    @Bean
    public Logger.Level feignLogLevel(){
        return Logger.Level.BASIC; // Set level to BASIC
    }
}

To apply it globally, add it to the @EnableFeignClients annotation in the startup class:

1
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration .class) 

To apply it to a specific service, add it to the @FeignClient annotation:

1
@FeignClient(value = "userService", configuration = DefaultFeignConfiguration .class)

Feign Optimization

Feign’s underlying HTTP client implementations:

  • URLConnection: Default, no connection pooling.
  • Apache HttpClient: Supports connection pooling.
  • OKHttp: Supports connection pooling.

Using a connection pool improves performance. Here is how to switch to HttpClient:

  1. Add the dependency:
1
2
3
4
5
<!-- HttpClient dependency -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>
  1. Configure the connection pool:
1
2
3
4
5
6
7
8
9
feign:
  client:
    config:
      default: # Global config
        loggerLevel: BASIC # Use BASIC for essential request/response info
  httpclient:
    enabled: true # Enable HttpClient support
    max-connections: 200 # Max total connections
    max-connections-per-route: 50 # Max connections per route

Best Practices

Best practices are optimized approaches based on common usage patterns.

The Feign client code often mirrors the provider’s controller:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// Feign Client
@FeignClient("userService")
public interface UserClient {
    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
}

// Controller
@GetMapping("/user/{id}")
public User queryById(@PathVariable("id") Long id) {
    return userService.queryById(id);
}

There are two ways to simplify this duplication:

Method 1: Inheritance

Define a common interface for both the consumer’s FeignClient and the provider’s Controller.

Define the interface:

1
2
3
4
public interface UserAPI{
    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
}

Feign Client implementation:

1
2
@FeignClient(value="userService")
public interface UserClient extends UserAPI{}

Controller implementation:

1
2
3
4
5
6
@RestController
public class UserController implements UserAPI{
    public User findById(@PathVariable("id") Long id){
        // implementation logic
    }
}

Pros: Simple and achieves code sharing.

Cons:

  • Tight coupling between provider and consumer.
  • Annotation mapping in parameter lists doesn’t inherit; you must redeclare methods, parameters, and annotations in the Controller.

Method 2: Extraction (Modularization)

Extract FeignClient into a standalone module. Place the interface, related POJOs, and default Feign configurations in this module for all consumers to use.

Create a new project and add the Feign starter dependency:

1
2
3
4
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

Import this feign-api dependency into the consumer project.

If the defined FeignClient is outside the scanning range of the SpringBootApplication, it won’t be picked up. Fix this in two ways:

  1. Specify the package to scan:
1
@EnableFeignClients(basePackages = "net.yexca.feign.clients")
  1. Specify the specific client classes to load:
1
@EnableFeignClients(clients = {UserClient.class})