OpenFeign

OpenFeign is a java library that simplifies creating REST clients in Spring applications. It allows to define a Java interface representing the API which we want to consume, and OpenFeign generates a dynamic implementation that handles the HTTP communication. This declarative approach reduces boilerplate code and improves development efficiency. FeignClient depend on abstractions, not on concretions. Meaning, coding to interfaces, not to concrete classes.

Key Points

Some of the key points are listed below.

  • Declarative API: With OpenFeign, we can define our HTTP client interfaces using annotations. This makes it easy to understand and maintain API contracts, as well as to create clients for RESTful services.

  • Integration with Spring Ecosystem: OpenFeign integrates well with the Spring ecosystem, making it a natural choice for Spring-based applications. It works well with other Spring features like dependency injection, auto-configuration, and Spring Boot.

  • Automatic Request Mapping: We can use annotations like @GetMapping, @PostMapping, etc., to map Java methods to HTTP requests. OpenFeign takes care of translating these annotations into actual HTTP requests, including handling path variables, query parameters, request bodies, etc.

  • Error Handling: OpenFeign provides mechanisms for handling HTTP errors, including status codes, exceptions, and error decoding. We can customize error handling based on application's needs.

  • Load Balancing and Service Discovery: OpenFeign integrates with Spring Cloud, allowing to easily implement client-side load balancing and service discovery using tools like Netflix Eureka or Spring Cloud LoadBalancer.

  • Interceptors: OpenFeign supports interceptors, which allows to intercept and modify outgoing requests and incoming responses. This provides flexibility for adding cross-cutting concerns like logging, authentication, or custom headers.

  • Async Support: OpenFeign offers asynchronous support for making non-blocking HTTP requests. This can improve the performance of applications, especially when dealing with high-latency or high-throughput scenarios.

  • Pluggable Serialization: OpenFeign supports pluggable serialization, allowing to use different serialization libraries like Jackson, Gson, or any custom serializer/deserializer.

  • Extensibility: OpenFeign is designed to be extensible, allowing to customize and extend its behavior as needed. We can create custom annotations, interceptors, error decoders, etc., to tailor OpenFeign to specific requirements.

  • OpenFeign primarily operates synchronously, meaning that when we make a request using a Feign client interface method, the calling thread will block until the response is received. However, OpenFeign does support asynchronous execution through the use of Completable futures or reactive programming. By integrating with reactive libraries like Project Reactor or RxJava, we can perform non-blocking asynchronous HTTP calls using Feign.

Additional notes

  • Feign uses standard Java interfaces, it's also easy to mock them during Unit Tests.

  • Feign integrates with Ribbon and Eureka Automatically and we can use Eureka Client ID instead of the URL. Also, works well with Hystrix for fall-back mechanism. With the fallback pattern, when a remote service call fails, rather than generating an exception, the service consumer will execute an alternative code path to try to carry out the action through another means.

    To achieve the goal, we need to enable Hystrix by adding feign.hystrix.enabled=true in the properties file.

@FeignClient(value = "jplaceholder",
  url = "http://localhost:8082",
  fallback = SomeApiFallback.class)
public interface SomeApiClient {
    // ...
}
  • One of the advantages of using Feign over RestTemplate is that, we do not need to write any implementation to call the other services. So there is no need to write Unit Test as implementation is automatically generated by Feign. However, we need to write Integration Tests.

  • Interfaces annotated with @FeignClient generate a synchronous implementation based on a thread-per-request model. So, for each request, the assigned thread blocks until it receives the response. The disadvantage of keeping multiple threads alive is that each open thread occupies memory and CPU cycles.

  • Feign supports multiple clients such as OkHttpClient, ApacheHttpClient, etc for different use case. Default is HttpClient from java.net package, provided by the Java platform, specifically HttpURLConnection for making HTTP requests.

  • Feign support multiple logging level. Feign logging occurs only on the DEBUG level. There are four logging levels to choose from:

    1. NONE – no logging, which is the default

    2. BASIC – log only the request method, URL and response status

    3. HEADERS – log the basic information together with request and response headers

    4. FULL – log the body, headers and metadata for both request and response

Commonly used annotations in OpenFeign

  1. @FeignClient (Required)

Placed at the interface level, it marks the interface as a Feign client.

Properties:

  • name (optional): A unique name for the client (used for bean creation). Defaults to the interface name.

  • url: Base URL of the remote service.

  • configuration: (optional) An array of classes implementing FeignConfiguration to customize client behavior (more advanced).

  • Other configuration options like path, pathPrefix, urlVar, etc. for defining request details.

  1. Interface Annotations (Method Level)

Define the HTTP method and request details for each API endpoint.

Common annotations include:

  • @GetMapping: Used for GET requests.

  • @PostMapping: Used for POST requests.

  • @PutMapping: Used for PUT requests.

  • @PatchMapping: Used for PATCH requests.

  • @DeleteMapping: Used for DELETE requests.

  • @RequestLine: Provides more flexibility for defining the entire request line with method, URI template, and headers directly in the annotation.

  1. @Param

Placed on method parameters to map them to template variables in the URI or request body. Used in conjunction with interface annotations like @GetMapping that support URI templates.

  1. @Headers (Method/Type Level)

Used to define request headers globally for the entire interface or for specific methods. Can be placed at the interface or method level.

  1. @QueryMap

Maps a method parameter of type Map<String, String> to query parameters in the request. Useful for sending multiple key-value pairs in the query string.

  1. @PathVariable

Used on method parameters to map them to path variables in the URI template defined in interface annotations.

  1. Other Annotations

OpenFeign supports various other annotations for advanced scenarios like:

  • @FeignDecoder: Specifies custom decoders for handling response data formats.

  • @FeignEncoder: Specifies custom encoders for marshalling request data.

  • @HeadersTarget: Used with @RequestLine to define header templates.

Example

Service 1 calling Service 2 GET API via OpenFeign

  • Service 2

Dependency

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

SampleController.java

package org.example.api;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SampleController {

    @GetMapping("/api/service-2/details")
    public ResponseEntity<String> getInterServiceDetails() {
        return ResponseEntity.ok("Service 2 processed successfully");
    }
}

Application.java

package org.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
       SpringApplication.run(Application.class);
    }
}

application.yaml

server:
  port: 8082

Build and run the application

  • Service 1

Dependency

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.30</version>
    <scope>provided</scope>
</dependency>

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>2021.0.5</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

Spring parent version used is given below

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.4</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

SampleController.java

package org.example.api;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.example.service.SampleService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RequiredArgsConstructor
@RestController
public class SampleController {

    private final SampleService sampleService;

    @GetMapping("/api/service-1/details")
    public String getInterServiceDetails() {
        return sampleService.processService2Details();
    }
}

Service2Client.java

package org.example.config;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

@FeignClient(name = "service2Client", url = "http://localhost:8082")
public interface Service2Client {

    @GetMapping("/api/service-2/details")
    String fetchService2Details();
}

SampleService.java

package org.example.service;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.example.config.Service2Client;
import org.springframework.stereotype.Service;

@Slf4j
@RequiredArgsConstructor
@Service
public class SampleService {

    private final Service2Client service2Client;

    // Method to retrieve service 2 details using a GET request
    public String processService2Details() {
        log.info("Calling service 2 API via OpenFeign");
        return service2Client.fetchService2Details();
    }
}

Main Application.java

package org.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@EnableFeignClients
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
       SpringApplication.run(Application.class);
    }
}

application.yaml

server:
  port: 8081

Build and run the application

Run the service 1 API from Postman

To Enable Feign logging, add the below bean and update application.yaml property file.

FeignClientConfiguration.java

package org.example.config;

import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FeignClientConfiguration {

    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}

application.yaml

logging:
  level:
    org:
      example:
        config:
          Service2Client: DEBUG

Hit the API again via Postman and monitor the logs.

Last updated