Top Related Projects
A type-safe HTTP client for Android and the JVM
Support for using OpenFeign in Spring Cloud apps
Unirest in Java: Simplified, lightweight HTTP client library.
Asynchronous Http and WebSocket Client library for Java
Java DSL for easy testing of REST services
Main Portal page for the Jackson project
Quick Overview
Feign is a Java HTTP client library that makes writing web service clients easier. It uses declarative interfaces and annotations to simplify HTTP API calls, allowing developers to create dynamic implementations of interfaces with minimal boilerplate code.
Pros
- Simplifies HTTP client code by using interfaces and annotations
- Supports various HTTP libraries (e.g., OkHttp, Apache HttpClient)
- Integrates well with Spring Boot and other frameworks
- Provides built-in support for error handling and retries
Cons
- Learning curve for developers new to declarative HTTP clients
- Can be overkill for simple API integrations
- Limited control over low-level HTTP details compared to direct use of underlying libraries
- May introduce additional complexity in large projects with many API integrations
Code Examples
- Defining a Feign client interface:
@FeignClient(name = "user-service", url = "https://api.example.com")
public interface UserClient {
@GetMapping("/users/{id}")
User getUserById(@PathVariable("id") Long id);
@PostMapping("/users")
User createUser(@RequestBody User user);
}
- Using a Feign client in a Spring Boot application:
@Service
public class UserService {
private final UserClient userClient;
@Autowired
public UserService(UserClient userClient) {
this.userClient = userClient;
}
public User getUser(Long id) {
return userClient.getUserById(id);
}
}
- Configuring Feign with custom error handling:
@Configuration
public class FeignConfig {
@Bean
public ErrorDecoder errorDecoder() {
return new CustomErrorDecoder();
}
}
public class CustomErrorDecoder implements ErrorDecoder {
@Override
public Exception decode(String methodKey, Response response) {
// Custom error handling logic
}
}
Getting Started
To use Feign in a Spring Boot project:
- Add the Feign dependency to your
pom.xml
:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- Enable Feign in your main application class:
@SpringBootApplication
@EnableFeignClients
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
-
Create a Feign client interface as shown in the code examples above.
-
Autowire and use the Feign client in your services or controllers.
Competitor Comparisons
A type-safe HTTP client for Android and the JVM
Pros of Retrofit
- More mature and widely adopted in the Android ecosystem
- Built-in support for RxJava and Coroutines
- Extensive documentation and community support
Cons of Retrofit
- Primarily focused on Android development
- Steeper learning curve for complex use cases
- Less flexible in terms of customization compared to Feign
Code Comparison
Retrofit:
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
Feign:
public interface GitHubClient {
@RequestLine("GET /users/{user}/repos")
List<Repo> listRepos(@Param("user") String user);
}
Both libraries aim to simplify HTTP API calls, but they have different approaches. Retrofit is more Android-centric and offers built-in support for popular Android libraries. Feign, on the other hand, is more flexible and can be used in various Java environments.
Retrofit's syntax is more annotation-driven, while Feign allows for more programmatic configuration. Feign also provides easier integration with Spring Boot applications.
Ultimately, the choice between Retrofit and Feign depends on the specific project requirements, target platform, and developer preferences. Retrofit excels in Android development, while Feign offers more versatility across different Java environments.
Support for using OpenFeign in Spring Cloud apps
Pros of spring-cloud-openfeign
- Seamless integration with Spring Boot and Spring Cloud ecosystem
- Supports Spring MVC annotations for declarative REST clients
- Provides additional features like load balancing and circuit breaking
Cons of spring-cloud-openfeign
- Heavier dependency footprint due to Spring Cloud integration
- Steeper learning curve for developers not familiar with Spring ecosystem
- May introduce unnecessary complexity for simpler projects
Code Comparison
spring-cloud-openfeign:
@FeignClient(name = "user-service")
public interface UserClient {
@GetMapping("/users/{id}")
User getUser(@PathVariable("id") Long id);
}
feign:
public interface UserClient {
@RequestLine("GET /users/{id}")
User getUser(@Param("id") Long id);
}
The main difference in the code examples is the use of Spring MVC annotations in spring-cloud-openfeign versus Feign's custom annotations in the original feign library. spring-cloud-openfeign allows developers to use familiar Spring annotations, making it more intuitive for those already working with Spring Boot applications. However, the original feign library provides a more lightweight approach that can be used in non-Spring projects as well.
Unirest in Java: Simplified, lightweight HTTP client library.
Pros of Unirest
- More flexible and supports a wider range of HTTP operations
- Simpler setup and configuration for basic use cases
- Built-in support for asynchronous requests
Cons of Unirest
- Less declarative approach compared to Feign's interface-based design
- Lacks built-in support for advanced features like retries and circuit breakers
- May require more boilerplate code for complex scenarios
Code Comparison
Unirest:
HttpResponse<JsonNode> response = Unirest.get("http://example.com/api")
.header("accept", "application/json")
.queryString("parameter", "value")
.asJson();
Feign:
@FeignClient("example-service")
public interface ExampleClient {
@GetMapping("/api")
JsonNode getApiData(@RequestParam("parameter") String value);
}
Summary
Unirest is a more versatile HTTP client library that offers simplicity for basic use cases and built-in support for asynchronous requests. It's suitable for developers who need flexibility and quick setup.
Feign, on the other hand, provides a more declarative approach with its interface-based design, making it ideal for creating type-safe HTTP clients. It integrates well with Spring Cloud and offers advanced features like load balancing and circuit breaking when used with other libraries.
The choice between the two depends on the specific requirements of your project, such as the need for declarative clients, integration with Spring ecosystem, or flexibility in HTTP operations.
Asynchronous Http and WebSocket Client library for Java
Pros of async-http-client
- Supports asynchronous and non-blocking HTTP operations
- Provides more fine-grained control over HTTP requests and responses
- Offers advanced features like WebSocket support and streaming
Cons of async-http-client
- Steeper learning curve compared to Feign's declarative approach
- Requires more boilerplate code for simple API interactions
- Less integration with Spring ecosystem
Code Comparison
async-http-client:
AsyncHttpClient client = Dsl.asyncHttpClient();
Future<Response> f = client.prepareGet("http://www.example.com/").execute();
Response r = f.get();
Feign:
interface ExampleApi {
@RequestLine("GET /")
String getRoot();
}
ExampleApi api = Feign.builder().target(ExampleApi.class, "http://www.example.com");
String response = api.getRoot();
Summary
async-http-client is a powerful library for handling complex HTTP scenarios with fine-grained control, while Feign offers a more declarative and simpler approach for API interactions. async-http-client excels in scenarios requiring advanced HTTP features and non-blocking operations, whereas Feign shines in simplifying REST API consumption, especially within the Spring ecosystem. The choice between the two depends on the specific requirements of your project and the level of control needed over HTTP operations.
Java DSL for easy testing of REST services
Pros of rest-assured
- More intuitive and expressive syntax for writing API tests
- Built-in support for JSON and XML parsing and validation
- Extensive support for various authentication methods
Cons of rest-assured
- Primarily focused on testing, not ideal for production API consumption
- Steeper learning curve for developers unfamiliar with BDD-style syntax
- Limited support for advanced HTTP client features
Code Comparison
rest-assured:
given()
.param("key", "value")
.header("Content-Type", "application/json")
.when()
.get("/api/resource")
.then()
.statusCode(200)
.body("field", equalTo("expected"));
feign:
@FeignClient("service-name")
public interface ApiClient {
@RequestMapping(method = RequestMethod.GET, value = "/api/resource")
ResponseEntity<Resource> getResource(@RequestParam("key") String value);
}
rest-assured is more suited for API testing with its expressive syntax and built-in assertions, while feign excels in creating type-safe HTTP clients for production use. rest-assured offers a more readable approach for test scenarios, whereas feign provides a clean interface-based method for defining API endpoints. The choice between the two depends on whether the primary use case is testing or production API consumption.
Main Portal page for the Jackson project
Pros of Jackson
- More versatile: Jackson is a general-purpose JSON processing library, offering broader functionality for JSON serialization/deserialization
- Extensive ecosystem: Provides modules for various data formats and integrations with other libraries
- High performance: Known for its efficient parsing and serialization capabilities
Cons of Jackson
- Steeper learning curve: More complex API due to its extensive features
- Heavier dependency: Larger library size compared to Feign's focused HTTP client functionality
Code Comparison
Jackson (JSON parsing):
ObjectMapper mapper = new ObjectMapper();
MyObject obj = mapper.readValue(jsonString, MyObject.class);
String json = mapper.writeValueAsString(obj);
Feign (HTTP client):
interface MyApi {
@RequestLine("GET /users/{id}")
User getUser(@Param("id") String id);
}
MyApi api = Feign.builder().target(MyApi.class, "https://api.example.com");
User user = api.getUser("123");
While both libraries serve different primary purposes, this comparison highlights their core functionalities. Jackson excels in JSON processing, offering a robust solution for various data manipulation tasks. Feign, on the other hand, simplifies HTTP API interactions with its declarative approach to creating HTTP clients.
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual CopilotREADME
Feign simplifies the process of writing Java HTTP clients
Feign is a Java to HTTP client binder inspired by Retrofit, JAXRS-2.0, and WebSocket. Feign's first goal was reducing the complexity of binding Denominator uniformly to HTTP APIs regardless of ReSTfulness.
Why Feign and not X?
Feign uses tools like Jersey and CXF to write Java clients for ReST or SOAP services. Furthermore, Feign allows you to write your own code on top of http libraries such as Apache HC. Feign connects your code to http APIs with minimal overhead and code via customizable decoders and error handling, which can be written to any text-based http API.
How does Feign work?
Feign works by processing annotations into a templatized request. Arguments are applied to these templates in a straightforward fashion before output. Although Feign is limited to supporting text-based APIs, it dramatically simplifies system aspects such as replaying requests. Furthermore, Feign makes it easy to unit test your conversions knowing this.
Java Version Compatibility
Feign 10.x and above are built on Java 8 and should work on Java 9, 10, and 11. For those that need JDK 6 compatibility, please use Feign 9.x
Feature overview
This is a map with current key features provided by feign:
Roadmap
Feign 11 and beyond
Making API clients easier
Short Term - What we're working on now. â°
- Response Caching
- Support caching of api responses. Allow for users to define under what conditions a response is eligible for caching and what type of caching mechanism should be used.
- Support in-memory caching and external cache implementations (EhCache, Google, Spring, etc...)
- Complete URI Template expression support
- Support level 1 through level 4 URI template expressions.
- Use URI Templates TCK to verify compliance.
Logger
API refactor- Refactor the
Logger
API to adhere closer to frameworks like SLF4J providing a common mental model for logging within Feign. This model will be used by Feign itself throughout and provide clearer direction on how theLogger
will be used.
- Refactor the
Retry
API refactor- Refactor the
Retry
API to support user-supplied conditions and better control over back-off policies. This may result in non-backward-compatible breaking changes
- Refactor the
Medium Term - What's up next. â²
- Async execution support via
CompletableFuture
- Allow for
Future
chaining and executor management for the request/response lifecycle. Implementation will require non-backward-compatible breaking changes. However this feature is required before Reactive execution can be considered.
- Allow for
- Reactive execution support via Reactive Streams
- For JDK 9+, consider a native implementation that uses
java.util.concurrent.Flow
. - Support for Project Reactor and RxJava 2+ implementations on JDK 8.
- For JDK 9+, consider a native implementation that uses
Long Term - The future âï¸
- Additional Circuit Breaker Support.
- Support additional Circuit Breaker implementations like Resilience4J and Spring Circuit Breaker
Usage
The feign library is available from Maven Central.
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
<version>??feign.version??</version>
</dependency>
Basics
Usage typically looks like this, an adaptation of the canonical Retrofit sample.
interface GitHub {
@RequestLine("GET /repos/{owner}/{repo}/contributors")
List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);
@RequestLine("POST /repos/{owner}/{repo}/issues")
void createIssue(Issue issue, @Param("owner") String owner, @Param("repo") String repo);
}
public static class Contributor {
String login;
int contributions;
}
public static class Issue {
String title;
String body;
List<String> assignees;
int milestone;
List<String> labels;
}
public class MyApp {
public static void main(String... args) {
GitHub github = Feign.builder()
.decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.com");
// Fetch and print a list of the contributors to this library.
List<Contributor> contributors = github.contributors("OpenFeign", "feign");
for (Contributor contributor : contributors) {
System.out.println(contributor.login + " (" + contributor.contributions + ")");
}
}
}
Interface Annotations
Feign annotations define the Contract
between the interface and how the underlying client
should work. Feign's default contract defines the following annotations:
Annotation | Interface Target | Usage |
---|---|---|
@RequestLine | Method | Defines the HttpMethod and UriTemplate for request. Expressions , values wrapped in curly-braces {expression} are resolved using their corresponding @Param annotated parameters. |
@Param | Parameter | Defines a template variable, whose value will be used to resolve the corresponding template Expression , by name provided as annotation value. If value is missing it will try to get the name from bytecode method parameter name (if the code was compiled with -parameters flag). |
@Headers | Method, Type | Defines a HeaderTemplate ; a variation on a UriTemplate . that uses @Param annotated values to resolve the corresponding Expressions . When used on a Type , the template will be applied to every request. When used on a Method , the template will apply only to the annotated method. |
@QueryMap | Parameter | Defines a Map of name-value pairs, or POJO, to expand into a query string. |
@HeaderMap | Parameter | Defines a Map of name-value pairs, to expand into Http Headers |
@Body | Method | Defines a Template , similar to a UriTemplate and HeaderTemplate , that uses @Param annotated values to resolve the corresponding Expressions . |
Overriding the Request Line
If there is a need to target a request to a different host then the one supplied when the Feign client was created, or you want to supply a target host for each request, include a
java.net.URI
parameter and Feign will use that value as the request target.@RequestLine("POST /repos/{owner}/{repo}/issues") void createIssue(URI host, Issue issue, @Param("owner") String owner, @Param("repo") String repo);
Templates and Expressions
Feign Expressions
represent Simple String Expressions (Level 1) as defined by URI Template - RFC 6570. Expressions
are expanded using
their corresponding Param
annotated method parameters.
Example
public interface GitHub {
@RequestLine("GET /repos/{owner}/{repo}/contributors")
List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repository);
class Contributor {
String login;
int contributions;
}
}
public class MyApp {
public static void main(String[] args) {
GitHub github = Feign.builder()
.decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.com");
/* The owner and repository parameters will be used to expand the owner and repo expressions
* defined in the RequestLine.
*
* the resulting uri will be https://api.github.com/repos/OpenFeign/feign/contributors
*/
github.contributors("OpenFeign", "feign");
}
}
Expressions must be enclosed in curly braces {}
and may contain regular expression patterns, separated by a colon :
to restrict
resolved values. Example owner
must be alphabetic. {owner:[a-zA-Z]*}
Request Parameter Expansion
RequestLine
and QueryMap
templates follow the URI Template - RFC 6570 specification for Level 1 templates, which specifies the following:
- Unresolved expressions are omitted.
- All literals and variable values are pct-encoded, if not already encoded or marked
encoded
via a@Param
annotation.
We also have limited support for Level 3, Path Style Expressions, with the following restrictions:
- Maps and Lists are expanded by default.
- Only Single variable templates are supported.
Examples:
{;who} ;who=fred
{;half} ;half=50%25
{;empty} ;empty
{;list} ;list=red;list=green;list=blue
{;map} ;semi=%3B;dot=.;comma=%2C
public interface MatrixService {
@RequestLine("GET /repos{;owners}")
List<Contributor> contributors(@Param("owners") List<String> owners);
class Contributor {
String login;
int contributions;
}
}
If owners
in the above example is defined as Matt, Jeff, Susan
, the uri will expand to /repos;owners=Matt;owners=Jeff;owners=Susan
For more information see RFC 6570, Section 3.2.7
Undefined vs. Empty Values
Undefined expressions are expressions where the value for the expression is an explicit null
or no value is provided.
Per URI Template - RFC 6570, it is possible to provide an empty value
for an expression. When Feign resolves an expression, it first determines if the value is defined, if it is then
the query parameter will remain. If the expression is undefined, the query parameter is removed. See below
for a complete breakdown.
Empty String
public void test() {
Map<String, Object> parameters = new LinkedHashMap<>();
parameters.put("param", "");
this.demoClient.test(parameters);
}
Result
http://localhost:8080/test?param=
Missing
public void test() {
Map<String, Object> parameters = new LinkedHashMap<>();
this.demoClient.test(parameters);
}
Result
http://localhost:8080/test
Undefined
public void test() {
Map<String, Object> parameters = new LinkedHashMap<>();
parameters.put("param", null);
this.demoClient.test(parameters);
}
Result
http://localhost:8080/test
See Advanced Usage for more examples.
What about slashes?
/
@RequestLine templates do not encode slash
/
characters by default. To change this behavior, set thedecodeSlash
property on the@RequestLine
tofalse
.
What about plus?
+
Per the URI specification, a
+
sign is allowed in both the path and query segments of a URI, however, handling of the symbol on the query can be inconsistent. In some legacy systems, the+
is equivalent to the a space. Feign takes the approach of modern systems, where a+
symbol should not represent a space and is explicitly encoded as%2B
when found on a query string.If you wish to use
+
as a space, then use the literalcharacter or encode the value directly as
%20
Custom Expansion
The @Param
annotation has an optional property expander
allowing for complete control over the individual parameter's expansion.
The expander
property must reference a class that implements the Expander
interface:
public interface Expander {
String expand(Object value);
}
The result of this method adheres to the same rules stated above. If the result is null
or an empty string,
the value is omitted. If the value is not pct-encoded, it will be. See Custom @Param Expansion for more examples.
Request Headers Expansion
Headers
and HeaderMap
templates follow the same rules as Request Parameter Expansion
with the following alterations:
- Unresolved expressions are omitted. If the result is an empty header value, the entire header is removed.
- No pct-encoding is performed.
See Headers for examples.
A Note on
@Param
parameters and their names:All expressions with the same name, regardless of their position on the
@RequestLine
,@QueryMap
,@BodyTemplate
, or@Headers
will resolve to the same value. In the following example, the value ofcontentType
, will be used to resolve both the header and path expression:public interface ContentService { @RequestLine("GET /api/documents/{contentType}") @Headers("Accept: {contentType}") String getDocumentByType(@Param("contentType") String type); }
Keep this in mind when designing your interfaces.
Request Body Expansion
Body
templates follow the same rules as Request Parameter Expansion
with the following alterations:
- Unresolved expressions are omitted.
- Expanded value will not be passed through an
Encoder
before being placed on the request body. - A
Content-Type
header must be specified. See Body Templates for examples.
Customization
Feign has several aspects that can be customized.
For simple cases, you can use Feign.builder()
to construct an API interface with your custom components.
For request setting, you can use options(Request.Options options)
on target()
to set connectTimeout, connectTimeoutUnit, readTimeout, readTimeoutUnit, followRedirects.
For example:
interface Bank {
@RequestLine("POST /account/{id}")
Account getAccountInfo(@Param("id") String id);
}
public class BankService {
public static void main(String[] args) {
Bank bank = Feign.builder()
.decoder(new AccountDecoder())
.options(new Request.Options(10, TimeUnit.SECONDS, 60, TimeUnit.SECONDS, true))
.target(Bank.class, "https://api.examplebank.com");
}
}
Multiple Interfaces
Feign can produce multiple api interfaces. These are defined as Target<T>
(default HardCodedTarget<T>
), which allow for dynamic discovery and decoration of requests prior to execution.
For example, the following pattern might decorate each request with the current url and auth token from the identity service.
public class CloudService {
public static void main(String[] args) {
CloudDNS cloudDNS = Feign.builder()
.target(new CloudIdentityTarget<CloudDNS>(user, apiKey));
}
class CloudIdentityTarget extends Target<CloudDNS> {
/* implementation of a Target */
}
}
Examples
Feign includes example GitHub and Wikipedia clients. The denominator project can also be scraped for Feign in practice. Particularly, look at its example daemon.
Integrations
Feign intends to work well with other Open Source tools. Modules are welcome to integrate with your favorite projects!
Encoder/Decoder
Gson
Gson includes an encoder and decoder you can use with a JSON API.
Add GsonEncoder
and/or GsonDecoder
to your Feign.Builder
like so:
public class Example {
public static void main(String[] args) {
GsonCodec codec = new GsonCodec();
GitHub github = Feign.builder()
.encoder(new GsonEncoder())
.decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.com");
}
}
Jackson
Jackson includes an encoder and decoder you can use with a JSON API.
Add JacksonEncoder
and/or JacksonDecoder
to your Feign.Builder
like so:
public class Example {
public static void main(String[] args) {
GitHub github = Feign.builder()
.encoder(new JacksonEncoder())
.decoder(new JacksonDecoder())
.target(GitHub.class, "https://api.github.com");
}
}
For the lighter weight Jackson Jr, use JacksonJrEncoder
and JacksonJrDecoder
from
the Jackson Jr Module.
Moshi
Moshi includes an encoder and decoder you can use with a JSON API.
Add MoshiEncoder
and/or MoshiDecoder
to your Feign.Builder
like so:
GitHub github = Feign.builder()
.encoder(new MoshiEncoder())
.decoder(new MoshiDecoder())
.target(GitHub.class, "https://api.github.com");
Sax
SaxDecoder allows you to decode XML in a way that is compatible with normal JVM and also Android environments.
Here's an example of how to configure Sax response parsing:
public class Example {
public static void main(String[] args) {
Api api = Feign.builder()
.decoder(SAXDecoder.builder()
.registerContentHandler(UserIdHandler.class)
.build())
.target(Api.class, "https://apihost");
}
}
JAXB
JAXB includes an encoder and decoder you can use with an XML API.
Add JAXBEncoder
and/or JAXBDecoder
to your Feign.Builder
like so:
public class Example {
public static void main(String[] args) {
Api api = Feign.builder()
.encoder(new JAXBEncoder())
.decoder(new JAXBDecoder())
.target(Api.class, "https://apihost");
}
}
SOAP
SOAP includes an encoder and decoder you can use with an XML API.
This module adds support for encoding and decoding SOAP Body objects via JAXB and SOAPMessage. It also provides SOAPFault decoding capabilities by wrapping them into the original javax.xml.ws.soap.SOAPFaultException
, so that you'll only need to catch SOAPFaultException
in order to handle SOAPFault.
Add SOAPEncoder
and/or SOAPDecoder
to your Feign.Builder
like so:
public class Example {
public static void main(String[] args) {
Api api = Feign.builder()
.encoder(new SOAPEncoder(jaxbFactory))
.decoder(new SOAPDecoder(jaxbFactory))
.errorDecoder(new SOAPErrorDecoder())
.target(MyApi.class, "http://api");
}
}
NB: you may also need to add SOAPErrorDecoder
if SOAP Faults are returned in response with error http codes (4xx, 5xx, ...)
Fastjson2
fastjson2 includes an encoder and decoder you can use with a JSON API.
Add Fastjson2Encoder
and/or Fastjson2Decoder
to your Feign.Builder
like so:
public class Example {
public static void main(String[] args) {
GitHub github = Feign.builder()
.encoder(new Fastjson2Encoder())
.decoder(new Fastjson2Decoder())
.target(GitHub.class, "https://api.github.com");
}
}
Contract
JAX-RS
JAXRSContract overrides annotation processing to instead use standard ones supplied by the JAX-RS specification. This is currently targeted at the 1.1 spec.
Here's the example above re-written to use JAX-RS:
interface GitHub {
@GET @Path("/repos/{owner}/{repo}/contributors")
List<Contributor> contributors(@PathParam("owner") String owner, @PathParam("repo") String repo);
}
public class Example {
public static void main(String[] args) {
GitHub github = Feign.builder()
.contract(new JAXRSContract())
.target(GitHub.class, "https://api.github.com");
}
}
Client
OkHttp
OkHttpClient directs Feign's http requests to OkHttp, which enables SPDY and better network control.
To use OkHttp with Feign, add the OkHttp module to your classpath. Then, configure Feign to use the OkHttpClient:
public class Example {
public static void main(String[] args) {
GitHub github = Feign.builder()
.client(new OkHttpClient())
.target(GitHub.class, "https://api.github.com");
}
}
Ribbon
RibbonClient overrides URL resolution of Feign's client, adding smart routing and resiliency capabilities provided by Ribbon.
Integration requires you to pass your ribbon client name as the host part of the url, for example myAppProd
.
public class Example {
public static void main(String[] args) {
MyService api = Feign.builder()
.client(RibbonClient.create())
.target(MyService.class, "https://myAppProd");
}
}
Java 11 Http2
Http2Client directs Feign's http requests to Java11 New HTTP/2 Client that implements HTTP/2.
To use New HTTP/2 Client with Feign, use Java SDK 11. Then, configure Feign to use the Http2Client:
GitHub github = Feign.builder()
.client(new Http2Client())
.target(GitHub.class, "https://api.github.com");
Breaker
Hystrix
HystrixFeign configures circuit breaker support provided by Hystrix.
To use Hystrix with Feign, add the Hystrix module to your classpath. Then use the HystrixFeign
builder:
public class Example {
public static void main(String[] args) {
MyService api = HystrixFeign.builder().target(MyService.class, "https://myAppProd");
}
}
Logger
SLF4J
SLF4JModule allows directing Feign's logging to SLF4J, allowing you to easily use a logging backend of your choice (Logback, Log4J, etc.)
To use SLF4J with Feign, add both the SLF4J module and an SLF4J binding of your choice to your classpath. Then, configure Feign to use the Slf4jLogger:
public class Example {
public static void main(String[] args) {
GitHub github = Feign.builder()
.logger(new Slf4jLogger())
.logLevel(Level.FULL)
.target(GitHub.class, "https://api.github.com");
}
}
Decoders
Feign.builder()
allows you to specify additional configuration such as how to decode a response.
If any methods in your interface return types besides Response
, String
, byte[]
or void
, you'll need to configure a non-default Decoder
.
Here's how to configure JSON decoding (using the feign-gson
extension):
public class Example {
public static void main(String[] args) {
GitHub github = Feign.builder()
.decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.com");
}
}
If you need to pre-process the response before give it to the Decoder, you can use the mapAndDecode
builder method.
An example use case is dealing with an API that only serves jsonp, you will maybe need to unwrap the jsonp before
send it to the Json decoder of your choice:
public class Example {
public static void main(String[] args) {
JsonpApi jsonpApi = Feign.builder()
.mapAndDecode((response, type) -> jsopUnwrap(response, type), new GsonDecoder())
.target(JsonpApi.class, "https://some-jsonp-api.com");
}
}
If any methods in your interface return type Stream
, you'll need to configure a StreamDecoder
.
Here's how to configure Stream decoder without delegate decoder:
public class Example {
public static void main(String[] args) {
GitHub github = Feign.builder()
.decoder(StreamDecoder.create((r, t) -> {
BufferedReader bufferedReader = new BufferedReader(r.body().asReader(UTF_8));
return bufferedReader.lines().iterator();
}))
.target(GitHub.class, "https://api.github.com");
}
}
Here's how to configure Stream decoder with delegate decoder:
public class Example {
public static void main(String[] args) {
GitHub github = Feign.builder()
.decoder(StreamDecoder.create((r, t) -> {
BufferedReader bufferedReader = new BufferedReader(r.body().asReader(UTF_8));
return bufferedReader.lines().iterator();
}, (r, t) -> "this is delegate decoder"))
.target(GitHub.class, "https://api.github.com");
}
}
Encoders
The simplest way to send a request body to a server is to define a POST
method that has a String
or byte[]
parameter without any annotations on it. You will likely need to add a Content-Type
header.
interface LoginClient {
@RequestLine("POST /")
@Headers("Content-Type: application/json")
void login(String content);
}
public class Example {
public static void main(String[] args) {
client.login("{\"user_name\": \"denominator\", \"password\": \"secret\"}");
}
}
By configuring an Encoder
, you can send a type-safe request body. Here's an example using the feign-gson
extension:
static class Credentials {
final String user_name;
final String password;
Credentials(String user_name, String password) {
this.user_name = user_name;
this.password = password;
}
}
interface LoginClient {
@RequestLine("POST /")
void login(Credentials creds);
}
public class Example {
public static void main(String[] args) {
LoginClient client = Feign.builder()
.encoder(new GsonEncoder())
.target(LoginClient.class, "https://foo.com");
client.login(new Credentials("denominator", "secret"));
}
}
@Body templates
The @Body
annotation indicates a template to expand using parameters annotated with @Param
. You will likely need to add a Content-Type
header.
interface LoginClient {
@RequestLine("POST /")
@Headers("Content-Type: application/xml")
@Body("<login \"user_name\"=\"{user_name}\" \"password\"=\"{password}\"/>")
void xml(@Param("user_name") String user, @Param("password") String password);
@RequestLine("POST /")
@Headers("Content-Type: application/json")
// json curly braces must be escaped!
@Body("%7B\"user_name\": \"{user_name}\", \"password\": \"{password}\"%7D")
void json(@Param("user_name") String user, @Param("password") String password);
}
public class Example {
public static void main(String[] args) {
client.xml("denominator", "secret"); // <login "user_name"="denominator" "password"="secret"/>
client.json("denominator", "secret"); // {"user_name": "denominator", "password": "secret"}
}
}
Headers
Feign supports settings headers on requests either as part of the api or as part of the client depending on the use case.
Set headers using apis
In cases where specific interfaces or calls should always have certain header values set, it makes sense to define headers as part of the api.
Static headers can be set on an api interface or method using the @Headers
annotation.
@Headers("Accept: application/json")
interface BaseApi<V> {
@Headers("Content-Type: application/json")
@RequestLine("PUT /api/{key}")
void put(@Param("key") String key, V value);
}
Methods can specify dynamic content for static headers using variable expansion in @Headers
.
public interface Api {
@RequestLine("POST /")
@Headers("X-Ping: {token}")
void post(@Param("token") String token);
}
In cases where both the header field keys and values are dynamic and the range of possible keys cannot
be known ahead of time and may vary between different method calls in the same api/client (e.g. custom
metadata header fields such as "x-amz-meta-*" or "x-goog-meta-*"), a Map parameter can be annotated
with HeaderMap
to construct a query that uses the contents of the map as its header parameters.
public interface Api {
@RequestLine("POST /")
void post(@HeaderMap Map<String, Object> headerMap);
}
These approaches specify header entries as part of the api and do not require any customizations when building the Feign client.
Setting headers per target
To customize headers for each request method on a Target, a RequestInterceptor can be used. RequestInterceptors can be shared across Target instances and are expected to be thread-safe. RequestInterceptors are applied to all request methods on a Target.
If you need per method customization, a custom Target is required, as the a RequestInterceptor does not have access to the current method metadata.
For an example of setting headers using a RequestInterceptor
, see the Request Interceptors
section.
Headers can be set as part of a custom Target
.
static class DynamicAuthTokenTarget<T> implements Target<T> {
public DynamicAuthTokenTarget(Class<T> clazz,
UrlAndTokenProvider provider,
ThreadLocal<String> requestIdProvider);
@Override
public Request apply(RequestTemplate input) {
TokenIdAndPublicURL urlAndToken = provider.get();
if (input.url().indexOf("http") != 0) {
input.insert(0, urlAndToken.publicURL);
}
input.header("X-Auth-Token", urlAndToken.tokenId);
input.header("X-Request-ID", requestIdProvider.get());
return input.request();
}
}
public class Example {
public static void main(String[] args) {
Bank bank = Feign.builder()
.target(new DynamicAuthTokenTarget(Bank.class, provider, requestIdProvider));
}
}
These approaches depend on the custom RequestInterceptor
or Target
being set on the Feign
client when it is built and can be used as a way to set headers on all api calls on a per-client
basis. This can be useful for doing things such as setting an authentication token in the header
of all api requests on a per-client basis. The methods are run when the api call is made on the
thread that invokes the api call, which allows the headers to be set dynamically at call time and
in a context-specific manner -- for example, thread-local storage can be used to set different
header values depending on the invoking thread, which can be useful for things such as setting
thread-specific trace identifiers for requests.
Advanced usage
Base Apis
In many cases, apis for a service follow the same conventions. Feign supports this pattern via single-inheritance interfaces.
Consider the example:
interface BaseAPI {
@RequestLine("GET /health")
String health();
@RequestLine("GET /all")
List<Entity> all();
}
You can define and target a specific api, inheriting the base methods.
interface CustomAPI extends BaseAPI {
@RequestLine("GET /custom")
String custom();
}
In many cases, resource representations are also consistent. For this reason, type parameters are supported on the base api interface.
@Headers("Accept: application/json")
interface BaseApi<V> {
@RequestLine("GET /api/{key}")
V get(@Param("key") String key);
@RequestLine("GET /api")
List<V> list();
@Headers("Content-Type: application/json")
@RequestLine("PUT /api/{key}")
void put(@Param("key") String key, V value);
}
interface FooApi extends BaseApi<Foo> { }
interface BarApi extends BaseApi<Bar> { }
Logging
You can log the http messages going to and from the target by setting up a Logger
. Here's the easiest way to do that:
public class Example {
public static void main(String[] args) {
GitHub github = Feign.builder()
.decoder(new GsonDecoder())
.logger(new Logger.JavaLogger("GitHub.Logger").appendToFile("logs/http.log"))
.logLevel(Logger.Level.FULL)
.target(GitHub.class, "https://api.github.com");
}
}
A Note on JavaLogger: Avoid using of default
JavaLogger()
constructor - it was marked as deprecated and will be removed soon.
The SLF4JLogger (see above) may also be of interest.
To filter out sensitive information like authorization or tokens
override methods shouldLogRequestHeader
or shouldLogResponseHeader
.
Request Interceptors
When you need to change all requests, regardless of their target, you'll want to configure a RequestInterceptor
.
For example, if you are acting as an intermediary, you might want to propagate the X-Forwarded-For
header.
static class ForwardedForInterceptor implements RequestInterceptor {
@Override public void apply(RequestTemplate template) {
template.header("X-Forwarded-For", "origin.host.com");
}
}
public class Example {
public static void main(String[] args) {
Bank bank = Feign.builder()
.decoder(accountDecoder)
.requestInterceptor(new ForwardedForInterceptor())
.target(Bank.class, "https://api.examplebank.com");
}
}
Another common example of an interceptor would be authentication, such as using the built-in BasicAuthRequestInterceptor
.
public class Example {
public static void main(String[] args) {
Bank bank = Feign.builder()
.decoder(accountDecoder)
.requestInterceptor(new BasicAuthRequestInterceptor(username, password))
.target(Bank.class, "https://api.examplebank.com");
}
}
Custom @Param Expansion
Parameters annotated with Param
expand based on their toString
. By
specifying a custom Param.Expander
, users can control this behavior,
for example formatting dates.
public interface Api {
@RequestLine("GET /?since={date}") Result list(@Param(value = "date", expander = DateToMillis.class) Date date);
}
Dynamic Query Parameters
A Map parameter can be annotated with QueryMap
to construct a query that uses the contents of the map as its query parameters.
public interface Api {
@RequestLine("GET /find")
V find(@QueryMap Map<String, Object> queryMap);
}
This may also be used to generate the query parameters from a POJO object using a QueryMapEncoder
.
public interface Api {
@RequestLine("GET /find")
V find(@QueryMap CustomPojo customPojo);
}
When used in this manner, without specifying a custom QueryMapEncoder
, the query map will be generated using member variable names as query parameter names. You can annotate a specific field of CustomPojo
with the @Param
annotation to specify a different name to the query parameter. The following POJO will generate query params of "/find?name={name}&number={number}®ion_id={regionId}" (order of included query parameters not guaranteed, and as usual, if any value is null, it will be left out).
public class CustomPojo {
private final String name;
private final int number;
@Param("region_id")
private final String regionId;
public CustomPojo (String name, int number, String regionId) {
this.name = name;
this.number = number;
this.regionId = regionId;
}
}
To setup a custom QueryMapEncoder
:
public class Example {
public static void main(String[] args) {
MyApi myApi = Feign.builder()
.queryMapEncoder(new MyCustomQueryMapEncoder())
.target(MyApi.class, "https://api.hostname.com");
}
}
When annotating objects with @QueryMap, the default encoder uses reflection to inspect provided objects Fields to expand the objects values into a query string. If you prefer that the query string be built using getter and setter methods, as defined in the Java Beans API, please use the BeanQueryMapEncoder
public class Example {
public static void main(String[] args) {
MyApi myApi = Feign.builder()
.queryMapEncoder(new BeanQueryMapEncoder())
.target(MyApi.class, "https://api.hostname.com");
}
}
Error Handling
If you need more control over handling unexpected responses, Feign instances can
register a custom ErrorDecoder
via the builder.
public class Example {
public static void main(String[] args) {
MyApi myApi = Feign.builder()
.errorDecoder(new MyErrorDecoder())
.target(MyApi.class, "https://api.hostname.com");
}
}
All responses that result in an HTTP status not in the 2xx range will trigger the ErrorDecoder
's decode
method, allowing
you to handle the response, wrap the failure into a custom exception or perform any additional processing.
If you want to retry the request again, throw a RetryableException
. This will invoke the registered
Retryer
.
Retry
Feign, by default, will automatically retry IOException
s, regardless of HTTP method, treating them as transient network
related exceptions, and any RetryableException
thrown from an ErrorDecoder
. To customize this
behavior, register a custom Retryer
instance via the builder.
The following example shows how to refresh token and retry with ErrorDecoder
and Retryer
when received a 401 response.
public class Example {
public static void main(String[] args) {
var github = Feign.builder()
.decoder(new GsonDecoder())
.retryer(new MyRetryer(100, 3))
.errorDecoder(new MyErrorDecoder())
.target(Github.class, "https://api.github.com");
var contributors = github.contributors("foo", "bar", "invalid_token");
for (var contributor : contributors) {
System.out.println(contributor.login + " " + contributor.contributions);
}
}
static class MyErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultErrorDecoder = new Default();
@Override
public Exception decode(String methodKey, Response response) {
// wrapper 401 to RetryableException in order to retry
if (response.status() == 401) {
return new RetryableException(response.status(), response.reason(), response.request().httpMethod(), null, response.request());
}
return defaultErrorDecoder.decode(methodKey, response);
}
}
static class MyRetryer implements Retryer {
private final long period;
private final int maxAttempts;
private int attempt = 1;
public MyRetryer(long period, int maxAttempts) {
this.period = period;
this.maxAttempts = maxAttempts;
}
@Override
public void continueOrPropagate(RetryableException e) {
if (++attempt > maxAttempts) {
throw e;
}
if (e.status() == 401) {
// remove Authorization first, otherwise Feign will add a new Authorization header
// cause github responses a 400 bad request
e.request().requestTemplate().removeHeader("Authorization");
e.request().requestTemplate().header("Authorization", "Bearer " + getNewToken());
try {
Thread.sleep(period);
} catch (InterruptedException ex) {
throw e;
}
} else {
throw e;
}
}
// Access an external api to obtain new token
// In this example, we can simply return a fixed token to demonstrate how Retryer works
private String getNewToken() {
return "newToken";
}
@Override
public Retryer clone() {
return new MyRetryer(period, maxAttempts);
}
}
Retryer
s are responsible for determining if a retry should occur by returning either a true
or
false
from the method continueOrPropagate(RetryableException e);
A Retryer
instance will be
created for each Client
execution, allowing you to maintain state bewteen each request if desired.
If the retry is determined to be unsuccessful, the last RetryException
will be thrown. To throw the original
cause that led to the unsuccessful retry, build your Feign client with the exceptionPropagationPolicy()
option.
Response Interceptor
If you need to treat what would otherwise be an error as a success and return a result rather than throw an exception then you may use a ResponseInterceptor
.
As an example Feign includes a simple RedirectionInterceptor
that can be used to extract the location header from redirection responses.
public interface Api {
// returns a 302 response
@RequestLine("GET /location")
String location();
}
public class MyApp {
public static void main(String[] args) {
// Configure the HTTP client to ignore redirection
Api api = Feign.builder()
.options(new Options(10, TimeUnit.SECONDS, 60, TimeUnit.SECONDS, false))
.responseInterceptor(new RedirectionInterceptor())
.target(Api.class, "https://redirect.example.com");
}
}
Metrics
By default, feign won't collect any metrics.
But, it's possible to add metric collection capabilities to any feign client.
Metric Capabilities provide a first-class Metrics API that users can tap into to gain insight into the request/response lifecycle.
A Note on Metrics modules:
All the metric-integrations are built in separate modules and not available in the
feign-core
module. You will need to add them to your dependencies.
Dropwizard Metrics 4
public class MyApp {
public static void main(String[] args) {
GitHub github = Feign.builder()
.addCapability(new Metrics4Capability())
.target(GitHub.class, "https://api.github.com");
github.contributors("OpenFeign", "feign");
// metrics will be available from this point onwards
}
}
Dropwizard Metrics 5
public class MyApp {
public static void main(String[] args) {
GitHub github = Feign.builder()
.addCapability(new Metrics5Capability())
.target(GitHub.class, "https://api.github.com");
github.contributors("OpenFeign", "feign");
// metrics will be available from this point onwards
}
}
Micrometer
public class MyApp {
public static void main(String[] args) {
GitHub github = Feign.builder()
.addCapability(new MicrometerCapability())
.target(GitHub.class, "https://api.github.com");
github.contributors("OpenFeign", "feign");
// metrics will be available from this point onwards
}
}
Static and Default Methods
Interfaces targeted by Feign may have static or default methods (if using Java 8+). These allows Feign clients to contain logic that is not expressly defined by the underlying API. For example, static methods make it easy to specify common client build configurations; default methods can be used to compose queries or define default parameters.
interface GitHub {
@RequestLine("GET /repos/{owner}/{repo}/contributors")
List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);
@RequestLine("GET /users/{username}/repos?sort={sort}")
List<Repo> repos(@Param("username") String owner, @Param("sort") String sort);
default List<Repo> repos(String owner) {
return repos(owner, "full_name");
}
/**
* Lists all contributors for all repos owned by a user.
*/
default List<Contributor> contributors(String user) {
MergingContributorList contributors = new MergingContributorList();
for(Repo repo : this.repos(owner)) {
contributors.addAll(this.contributors(user, repo.getName()));
}
return contributors.mergeResult();
}
static GitHub connect() {
return Feign.builder()
.decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.com");
}
}
Async execution via CompletableFuture
Feign 10.8 introduces a new builder AsyncFeign
that allow methods to return CompletableFuture
instances.
interface GitHub {
@RequestLine("GET /repos/{owner}/{repo}/contributors")
CompletableFuture<List<Contributor>> contributors(@Param("owner") String owner, @Param("repo") String repo);
}
public class MyApp {
public static void main(String... args) {
GitHub github = AsyncFeign.builder()
.decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.com");
// Fetch and print a list of the contributors to this library.
CompletableFuture<List<Contributor>> contributors = github.contributors("OpenFeign", "feign");
for (Contributor contributor : contributors.get(1, TimeUnit.SECONDS)) {
System.out.println(contributor.login + " (" + contributor.contributions + ")");
}
}
}
Initial implementation include 2 async clients:
AsyncClient.Default
AsyncApacheHttp5Client
Mavenâs Bill of Material (BOM)
Keeping all feign libraries on the same version is essential to avoid incompatible binaries. When consuming external dependencies, can be tricky to make sure only one version is present.
With that in mind, feign build generates a module called feign-bom
that locks the versions for all feign-*
modules.
The Bill Of Material is a special POM file that groups dependency versions that are known to be valid and tested to work together. This will reduce the developersâ pain of having to test the compatibility of different versions and reduce the chances to have version mismatches.
Here is one example of what feign BOM file looks like.
Usage
<project>
...
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-bom</artifactId>
<version>??feign.version??</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
Top Related Projects
A type-safe HTTP client for Android and the JVM
Support for using OpenFeign in Spring Cloud apps
Unirest in Java: Simplified, lightweight HTTP client library.
Asynchronous Http and WebSocket Client library for Java
Java DSL for easy testing of REST services
Main Portal page for the Jackson project
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual Copilot