FeignClient - Synchronous Communication
Feign is a Declarative HTTP client developed by Netflix. Declarative means "we tell What to do, not How to do".
FeignClient
Synchronous Communication
Feign is a Declarative HTTP client developed by Netflix.
1. FeignClient
Feign is a Declarative HTTP client developed by Netflix.
Declarative means "we tell What to do, not How to do".
In SpringBoot, Feign capability is available via Spring Cloud OpenFeign library.
Spring Cloud provides a set of tool and libraries, which helps to build distributed microservices. As it provides seamless integration with:
2. Pom.xml
Dependency
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>In future, we might use more Spring Cloud libraries (for Load balancer, for Service Discovery etc.) and all those Spring Cloud libraries should have compatible version, therefore we use below dependency management, so that we don't have to mange it manually.
That’s why we are not specifying the version with above "spring-cloud-starter-openfeign" dependency, it will be taken care by our dependency management.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2023.0.1</version> <!-- Use latest compatible version-->
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>3. OrderService → ProductService
ProductClient
@FeignClient(
name = "product-service",
url = "${feign.client.product-service.url}"
)
public interface ProductClient {
@GetMapping("/products/{id}")
String getProductById(@PathVariable("id") String id);
}ProductController
@RestController
@RequestMapping("/products")
public class ProductController {
@GetMapping("/{id}")
public String getProduct(@PathVariable String id) {
return "Product fetched with id: " + id;
}
}application.properties
server.port=8081
# Base URL for Product Service
feign.client.product-service.url=http://localhost:8082OrderController
@RestController
@RequestMapping("/orders")
public class OrderController {
@Autowired
ProductClient productClient;
@GetMapping("/{id}")
public ResponseEntity<String> getOrder(@PathVariable String id) {
String responseFromProductAPI =
productClient.getProductById(id);
System.out.println(
"Response from Product api call is: " +
responseFromProductAPI
);
return ResponseEntity.ok("order call successful");
}
}OrderserviceApplication
@SpringBootApplication
@EnableFeignClients
public class OrderserviceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderserviceApplication.class, args);
}
}@EnableFeignClients enable Feign support and tells SpringBoot to scan for interfaces annotated with @FeignClient
Start the application and invoke the Order Endpoint.


4. How Declarative HTTP Calls Works?

So, first important thing to understand is, how this Declarative HTTP Calls works?
MethodHandler also has method invoke(), which knows how to create HTTP request object from above info and make a HTTP call.

5. Annotation Usage Demo
An example below, with various annotation usage demo like @GetMapping, @PutMapping, @PathVariable, @RequetParam, @RequestHeader, @RequestBody etc..
Order Application — ProductClient
@FeignClient(
name = "product-service",
url = "${feign.client.product-service.url}"
)
public interface ProductClient {
@GetMapping("/products/{id}")
String getProductById(@PathVariable("id") String id);
@PutMapping(
value = "/products/update/{id}",
consumes = "application/json"
)
Product updateProduct(
/*
Ordering is not mandatory,
Springboot will take care to pass
values according to annotations.
*/
@PathVariable("id") String id,
@RequestParam("sendMail") boolean sendMail,
@RequestHeader("X-ConceptCoding-ID") String uniqueID,
@RequestBody Product updatedProductDetails
);
}Product Application — ProductController

@RestController
@RequestMapping("/products")
public class ProductController {
@GetMapping("/{id}")
public ResponseEntity<String> getProduct(@PathVariable String id) {
return ResponseEntity.ok("fetch the product details with id:" + id);
}
@PutMapping("/update/{id}")
public ResponseEntity<Product> createProduct(
@PathVariable String id,
@RequestBody Product product,
@RequestParam("sendMail") boolean sendMail,
@RequestHeader("X-ConceptCoding-ID") String uniqueID
) {
Product dbProductObject = getProductFromDB(id);
dbProductObject.setName(product.getName());
return ResponseEntity.ok(dbProductObject);
}
}6. Encoder and Decoder in FeignClient
- Encoder: Converts a Java object into a request body (say JSON).
- Decoder: Converts the HTTP response body (say JSON) into a Java object.
If we want our custom Encoder and Decoder implementation:

ProductClient
@FeignClient(
name = "product-service",
url = "${feign.client.product-service.url}",
configuration = ProductClientConfig.class
)
public interface ProductClient {
@PutMapping(
value = "/products/update/{id}",
consumes = "application/json"
)
Product updateProduct(
@PathVariable("id") String id,
@RequestParam("sendMail") boolean sendMail,
@RequestHeader("X-ConceptCoding-ID") String uniqueID,
@RequestBody Product updatedProductDetails
);
}MyCustomProductClientEncoder
public class MyCustomProductClientEncoder implements Encoder {
@Override
public void encode(
Object object,
Type bodyType,
RequestTemplate template
) throws EncodeException {
try {
String jsonString =
new ObjectMapper().writeValueAsString(object);
template.body(jsonString);
} catch (Exception e) {
throw new EncodeException("Unable to encode object");
}
}
}MyCustomProductClientDecoder
public class MyCustomProductClientDecoder implements Decoder {
@Override
public Object decode(Response response, Type type)
throws IOException, DecodeException, FeignException {
InputStream responseBody =
response.body().asInputStream();
return new ObjectMapper().readValue(
responseBody,
new TypeReference<Object>() {
@Override
public Type getType() {
return type;
}
}
);
}
}ProductClientConfig
@Configuration
public class ProductClientConfig {
@Bean
public Encoder myCustomEncoder() {
return new MyCustomProductClientEncoder();
}
@Bean
public Decoder myCustomDecoder() {
return new MyCustomProductClientDecoder();
}
}7. ErrorDecoder in FeignClient
It is used to handle non 2xx status codes like 4xx and 5xx.
If we want our custom ErrorDecoder implementation:

ProductClient
@FeignClient(
name = "product-service",
url = "${feign.client.product-service.url}",
configuration = ProductClientConfig.class
)
public interface ProductClient {
@PutMapping(
value = "/products/update/{id}",
consumes = "application/json"
)
Product updateProduct(
@PathVariable("id") String id,
@RequestParam("sendMail") boolean sendMail,
@RequestHeader("X-ConceptCoding-ID") String uniqueID,
@RequestBody Product updatedProductDetails
);
}MyCustomProductClientErrorDecoder
public class MyCustomProductClientErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultErrorDecoder = new Default();
@Override
public Exception decode(String methodKey, Response response) {
HttpStatus statusCode = HttpStatus.valueOf(response.status());
if (statusCode.is4xxClientError()) {
return new MyCustomBadRequestException("Client Error");
} else if (statusCode.is5xxServerError()) {
return new MyCustomServerException("Server Error");
} else {
return defaultErrorDecoder.decode(methodKey, response);
}
}
}ProductClientConfig
@Configuration
public class ProductClientConfig {
@Bean
public ErrorDecoder myCustomErrorDecoder() {
return new MyCustomProductClientErrorDecoder();
}
}8. Retryer in FeignClient



Retry only happens when there is either:
- Connection time out
- Network related exception like (IOException)
After all retry finished, then ErrorDecoder is invoked.
For 4xx and 5xx, retry do not happens, its handled by ErrorDecoder directly.
After all retry attempt finished, ErrorDecoder is invoked.
If, we don’t want to retry at all, we can use Retryer.NEVER_RETRY (this already present in Retryer class)
@Configuration
public class ProductClientConfig {
@Bean
public Retryer myCustomRetryer() {
return Retryer.NEVER_RETRY;
}
}If, we want custom implementation
UserCase-1: I only want to control Attempt, wait time and max period, rest I want to reuse the "Retryer.Default" logic.
public class MyCustomRetryer extends Retryer.Default {
// control attempts & wait time, rest uses Default implementation
public MyCustomRetryer() {
super(200, 1000, 4); // period, maxPeriod, maxAttempts
}
}@Configuration
public class ProductClientConfig {
@Bean
public Retryer myCustomRetryer() {
return new MyCustomRetryer();
}
}UserCase-2: I want full control, then I have to implement Retryer itself and provide the custom implementation for the "continueOrPropogate()" method.
public class MyCustomRetryer implements Retryer {
private int attempt = 1;
private final int maxAttempts = 5;
@Override
public void continueOrPropagate(RetryableException e) {
if (attempt >= maxAttempts) {
throw e;
}
attempt++;
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
// ignore
}
}
@Override
public Retryer clone() {
return new MyCustomRetryer();
}
}9. FeignClient Name and Timeout Configuration
Last but not the least:
During start, we discussed that, this name is just a arbitrary value. And its just we are giving the name to our FeignClient.
But where its exactly used?
Yes, this name comes handy, when we have to provide any configuration in application.properties.
If we want to set request and connection timeout only for product-service FeignClient
application.properties
# timeout applicable only to product-service FeignClient
feign.client.config.product-service.connectTimeout=3000
feign.client.config.product-service.readTimeout=5000If we want to set request and connection timeout for all FeignClient
application.properties
# timeout applicable to all FeignClients
feign.client.config.default.connectTimeout=3000
feign.client.config.default.readTimeout=5000