Table of Contents
Introduction
In today’s interconnected digital world, REST APIs (Representational State Transfer Application Programming Interfaces) have become essential for enabling communication between different systems, applications, and services. Whether you’re integrating with third-party services or building microservices architectures, REST APIs allow developers to access and manipulate resources over the web in a standardized way.
When it comes to consuming REST APIs in Java, Feign is one of the most popular and powerful tools available. Feign is a declarative web service client that simplifies the process of calling REST endpoints, making it easier for developers to interact with external APIs by writing clean, readable code.
In this guide, we’ll dive into how to consume REST APIs using Feign in a step-by-step manner, complete with practical examples to help you get started quickly and efficiently.
What is Feign?
Feign is an open-source, declarative web service client developed by Netflix, designed to simplify the process of making HTTP requests to REST APIs. In essence, Feign allows developers to write clean, easy-to-understand code that interacts with RESTful services, without the boilerplate often required when using traditional HTTP clients.
With Feign, you define how to consume REST APIs by simply creating interfaces that describe API endpoints and HTTP methods (GET, POST, etc.). Feign then generates the necessary HTTP calls at runtime, abstracting away the complexities of low-level HTTP communication. This declarative approach enables developers to focus more on the business logic rather than the details of building and managing HTTP connections, making code more maintainable and readable.
Feign is particularly useful in microservices architectures, where services frequently need to communicate with each other via REST APIs. It integrates seamlessly with Spring Cloud, allowing for easy configuration, load balancing, error handling, and more, making it the go-to choice for REST API consumption in Java microservice environments.
By using Feign, developers can:
- Avoid the repetitive boilerplate code required for building REST clients.
- Leverage annotations to define API behavior, making the code more intuitive.
- Easily integrate with other libraries such as Spring Boot and Spring Cloud, adding advanced functionality like circuit breakers, retries, and authentication.
In summary, Feign plays a crucial role in simplifying REST API consumption by offering a declarative and flexible approach, which significantly enhances developer productivity and code quality.
Feign vs OKHttpClient vs RestTemplate
When making REST API calls in Java, you have multiple options depending on your needs. OKHttpClient is a powerful, low-level HTTP client that provides complete control over the request. In the example below, we use OKHttpClient to fetch posts from https://jsonplaceholder.typicode.com/posts
:
public class Main {
public static void main(String[] args) throws IOException {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://jsonplaceholder.typicode.com/posts")
.get()
.addHeader("cache-control", "no-cache")
.build();
Response response = client.newCall(request).execute();
ObjectMapper objectMapper = new ObjectMapper();
PostDTO[] allPosts = objectMapper.readValue(response.body().string(), PostDTO[].class);
}
}
In this example, the code handles everything from setting headers to parsing the response, offering flexibility but requiring more boilerplate.
Next, RestTemplate, which simplifies interaction with REST APIs by abstracting away the HTTP connection. Using RestTemplate to fetch the same posts looks like this:
@Service
public class PostsClientService {
public PostDTO[] getAllPosts() {
RestTemplate restTemplate = new RestTemplate();
return restTemplate.getForObject("https://jsonplaceholder.typicode.com/posts", PostDTO[].class);
}
}
RestTemplate simplifies the process of making HTTP calls by automatically handling response deserialization. However, error handling requires additional code, such as a try-catch
block for exceptions like HttpClientErrorException
.
Why Use Feign When We Already Have RestTemplate and OKHttpClient?
Handling errors, custom encoders, or decoders in REST API calls can quickly lead to more code often repeated across different methods making the codebase harder to maintain. Netflix faced these same challenges and created its REST client using Spring’s capabilities, which they called Feign.
Feign was originally part of the Spring Cloud Netflix suite alongside tools like Eureka, Zuul, and Hystrix. However, in 2018, Netflix stopped supporting most of these libraries, and the open-source community took over development. Feign was then renamed OpenFeign.
In this article, we’ll focus on OpenFeign, which, as described in the Spring documentation, is a “Declarative REST Client” that creates a dynamic implementation of interfaces decorated with JAX-RS or Spring MVC annotations. Just like Spring Data, OpenFeign uses these abstractions with the @FeignClient
annotation, allowing you to easily set headers, request bodies, and return types.
Here’s how our example would look using OpenFeign:
@FeignClient(name = "posts-client", url = "https://jsonplaceholder.typicode.com/posts")
public interface PostsClient {
@GetMapping
List<PostDTO> getAllPosts();
}
So, why should you choose Feign?
One of the biggest advantages is that you don’t need to learn any new syntax. You can continue using familiar Spring MVC annotations like @RequestMapping
, @PathVariable
, @RequestParam
, @RequestBody
, @ResponseBody
, @RequestHeader
, and @ResponseHeader
. This makes your code easy to read and maintain.
Feign abstracts the complexity of how requests are made, allowing you to focus solely on configuration rather than the underlying implementation. Each Feign client consists of several customizable components:
- Decoder: This processes the incoming server response and transforms it into the desired object type, typically using a
ResponseEntityDecoder
. - Encoder: Before calling another service, you can process the outgoing object with a default encoder, which is the
SpringEncoder
. - Logger: You can set the logging level according to your needs. These logs are available in DEBUG mode, and you can implement any custom logger that extends
Slf4jLogger
. - Contract: This defines which annotations are valid for your clients. For our examples, we use
SpringMvcContract
to enable the use of Spring MVC annotations.
In short, Feign streamlines your REST API interactions while leveraging your existing knowledge of Spring.
Call external services using Feign
Let’s take a closer look at Feign clients and explore how to create and configure them for making external service calls using both POST and GET methods. We’ll also cover how to pass query parameters, path variables, HTTP headers, and request bodies.
How to Pass Query Parameters
In this example, our PostsClient
will interact with the REST service at https://jsonplaceholder.typicode.com/posts
. This service accepts a query parameter called userId
. Based on the value provided, it returns only the posts associated with that specific user. The full request URL would look like this: https://jsonplaceholder.typicode.com/posts?userId=1
.
package com.feigndemo.demo.posts;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@FeignClient(name = "posts-client", url = "https://jsonplaceholder.typicode.com/posts")
public interface PostsClient {
@GetMapping
List<PostDTO> getAllPosts();
@GetMapping
List<PostDTO> getAllCountriesByUserId(@RequestParam("userId") Integer userId);
}
This will automatically include a query parameter named “userId” in the request URL when you call the remote service. Now, you can use the client just like any other Spring service, simply passing in the required integer value.
How to Pass Path Variables
Path variables are segments of the URL that change based on the specific resource you want to access hence the name “variables.” To set up path variables in Feign clients, you’ll need to do two things:
- Identify which part of the URL the path variable will replace.
- Specify the value that should be used in that part of the path.
For instance, let’s say we want to consume the API at https://jsonplaceholder.typicode.com/posts/{postId}
, where postId
is the path variable.
We can easily turn this into a Feign client method. To connect the path variable to a method argument, we’ll use the @PathVariable
annotation from Spring Web.
@FeignClient(name = "posts-client", url = "https://jsonplaceholder.typicode.com/posts")
public interface PostsClient {
@GetMapping("{postId}")
PostDTO getPostByPostId(@PathVariable("postId") Integer postId);
}
Making a POST Request
Now, let’s talk about how to create a new post using the Feign client. In this example, the API we’re working with accepts a request body in JSON format. Thankfully, we can achieve this easily by using the same Spring Web annotations we’re already familiar with.
@FeignClient(name = "posts-client", url = "https://jsonplaceholder.typicode.com/posts")
public interface PostsClient {
@PostMapping
PostDTO create(@RequestBody PostDTO post);
}
A Slightly More Complex Scenario
While creating a new post is straightforward, let’s make things a bit more interesting. Imagine the API we’re using has the following specifications:
- POST URL:
https://jsonplaceholder.typicode.com/posts
- Content-Type:
application/x-www-form-urlencoded
To accommodate this, we’ll set up a PostsController
that exposes a new endpoint:
- POST URL:
http://localhost:8081/api/posts
- Content-Type:
application/json
Here’s how our controller will look:
@RestController
@AllArgsConstructor
@RequestMapping("/api/posts")
public class PostsController {
private final PostsClient postsClient;
@PostMapping
public PostDTO create(@RequestBody PostDTO post) {
return postsClient.create(post);
}
}
Making the Request: Uh-Oh, We Have an Error!
Let’s try to make our request, and oops! We hit an error:
feign.codec.EncodeException: Could not write request: no suitable HttpMessageConverter found for request type [com.feigndemo.demo.posts.PostDTO] and content type [application/x-www-form-urlencoded]
The key part to notice here is “EncodeException.” This tells us exactly where things went wrong: we haven’t defined an encoder for our request object yet.
Since we’re working with a Content-Type
of application/x-www-form-urlencoded
, the data needs to be sent as a key-value pair, much like how form data is structured. Luckily, Feign provides a built-in encoder for this type of data, called FormEncoder
.
Now, we need to override the default encoder in our configuration class. Here’s how that looks:
package com.feigndemo.demo.posts;
import feign.codec.Encoder;
import feign.form.FormEncoder;
import org.springframework.context.annotation.Bean;
public class FeignSimpleEncoderConfig {
@Bean
public Encoder encoder() {
return new FormEncoder();
}
}
With this configuration in place, we can update our PostsClient
like this:
@FeignClient(name = "posts-client",
url = "https://jsonplaceholder.typicode.com/posts",
configuration = FeignSimpleEncoderConfig.class)
public interface PostsClient {
@PostMapping(consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
PostDTO create(PostDTO post);
}
Now, if you send the request again, you should receive a successful response from the server!
Handling Errors with Feign
not all API requests succeed. So, how does Feign help us manage errors? Effectively handling errors is crucial; without it, you could face unexpected issues that are harder to troubleshoot. A well-implemented error management strategy can provide valuable insights, making it easier for your team to investigate problems when they arise.
By default, Feign throws a FeignException
whenever something goes wrong. However, you might prefer to have application-specific exceptions instead. Thankfully, this is simple to achieve by creating your own implementation of the ErrorDecoder
interface.
To use ErrorDecoder
, you’ll first need to create a new Java class that implements this interface and overrides the decode
method:
public Exception decode(String methodKey, Response response)
Implementing the ErrorDecoder
interface gives you access to two key objects: methodKey
and Response
.
- methodKey: This contains the name of the Feign client class and the specific method.
- response: This allows you to access the HTTP status code, the body of the HTTP response, and the request object itself. You can use this information to craft a more informative error message.
Here’s an example of how you can implement the ErrorDecoder
interface. This implementation does two main things:
- Logs the error along with the status, the method that caused it, and the response body.
- Returns a server error status for client errors.
public class PostsClientErrorDecoder implements ErrorDecoder {
private static final Logger LOG = LoggerFactory.getLogger(PostsClientErrorDecoder.class);
private final ErrorDecoder defaultDecoder = new ErrorDecoder.Default();
@Override
public Exception decode(String methodKey, Response response) {
Response.Body responseBody = response.body();
HttpStatus responseStatus = HttpStatus.valueOf(response.status());
Exception defaultException = defaultDecoder.decode(methodKey, response);
// Log the error first, including the response body
try {
LOG.error(
"Got {} response from {}, response body: {}",
response.status(),
methodKey,
IOUtils.toString(responseBody.asReader(Charset.defaultCharset()))
);
} catch (IOException e) {
LOG.error(
"Got {} response from {}, but the response body could not be read",
response.status(),
methodKey
);
}
if (responseStatus.is4xxClientError()) {
// Return a 500 error on client errors
return new RestApiClientException(
"Client error " + response.status() + " from calling posts API",
defaultException
);
}
return defaultException;
}
}
And here’s how you can define the custom exception for your API client:
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public class RestApiClientException extends Exception {
public RestApiClientException(String message, Exception exception) {
super(message, exception);
}
}
With this setup, you’ll have a robust error-handling mechanism that not only logs the errors but also provides clearer feedback for your application.
Feign Client: Adding Retry Logic for Server Errors
Let’s enhance our example by introducing a retry mechanism: suppose we want to automatically retry any server error up to five times.
To implement this, we need to add a retry bean in our configuration file, as shown below:
public class FeignSimpleEncoderConfig {
@Bean
public Encoder encoder() {
return new FormEncoder();
}
@Bean
public ErrorDecoder errorDecoder() {
return new PostsClientErrorDecoder();
}
@Bean
public Retryer retryer() {
return new Retryer.Default();
}
}
The retry mechanism is triggered only when a RetryableException
occurs.
Now, let’s add some logic to our PostsClientErrorDecoder
class. Here’s how you can do it:
package com.feigndemo.demo.posts;
import feign.Response;
import feign.RetryableException;
import feign.codec.ErrorDecoder;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
import java.io.IOException;
import java.nio.charset.Charset;
public class PostsClientErrorDecoder implements ErrorDecoder {
private static final Logger LOG = LoggerFactory.getLogger(PostsClientErrorDecoder.class);
private final ErrorDecoder defaultDecoder = new ErrorDecoder.Default();
@Override
public Exception decode(String methodKey, Response response) {
Response.Body responseBody = response.body();
HttpStatus responseStatus = HttpStatus.valueOf(response.status());
Exception defaultException = defaultDecoder.decode(methodKey, response);
try {
LOG.error(
"Received {} response from {}, response body: {}",
response.status(),
methodKey,
IOUtils.toString(responseBody.asReader(Charset.defaultCharset()))
);
} catch (IOException e) {
LOG.error(
"Received {} response from {}, but the response body could not be read",
response.status(),
methodKey
);
}
if (responseStatus.is4xxClientError()) {
return new RestApiClientException(
"Client error " + response.status() + " while calling the posts API",
defaultException
);
} else if (responseStatus.is5xxServerError()) {
return new RetryableException(
responseStatus.value(),
"Server error occurred",
response.request().httpMethod(),
null,
response.request()
);
}
return defaultException;
}
}
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
class RestApiClientException extends Exception {
public RestApiClientException(String message, Exception exception) {
super(message, exception);
}
}
With this implementation, we now have a built-in retry mechanism that will automatically retry the request up to five times by default in case of server errors. You can customize this behavior as needed to suit your application’s requirements!
FAQ about consume rest using Feigin
What is Feign in Java?
Feign is an open-source, declarative web service client that simplifies the process of calling REST APIs by allowing developers to define HTTP requests through interfaces. It abstracts HTTP communication complexities, making code cleaner and easier to maintain.
Why use Feign over RestTemplate or OKHttpClient?
Feign simplifies API calls by reducing boilerplate code, leveraging annotations, and seamlessly integrating with Spring Cloud. It allows developers to focus on business logic rather than managing HTTP requests, unlike RestTemplate or OKHttpClient which require more manual handling.
How can I handle query parameters and path variables in Feign?
Feign allows passing query parameters using @RequestParam
and path variables using @PathVariable
annotations. It automatically constructs the appropriate URL for API calls based on these parameters.
How do you handle errors with Feign?
Feign provides a default error handling mechanism through FeignException
. However, custom error handling can be implemented by using the ErrorDecoder
interface, which allows defining more specific responses based on HTTP status codes.
Can Feign retry failed requests?
Yes, Feign supports retry logic for handling server errors. You can configure it to automatically retry requests in case of failures, improving resilience in microservice architectures.
Conclusion
In today’s tech landscape, effectively consuming REST APIs is crucial, and Feign shines as a powerful tool. It simplifies the process of consuming REST APIs using Feign by allowing you to define HTTP requests with minimal hassle, enhancing code readability and maintainability.
One of Feign’s standout features is its Feign Client Exception Handling capabilities, which enable you to create custom error decoders. This not only aids in debugging but also improves user experience by effectively managing server responses. Moreover, with Retrying Feign Calls, your application can automatically retry failed requests, adding resilience against transient errors.
When comparing Feign Client vs RestTemplate vs OkHttpClient, each has its strengths. However, Feign’s declarative style and seamless integration with Spring MVC make it particularly appealing for modern applications.