Request Customization
About
OpenFeign supports extensive request customization without cluttering our service code. It allows setting custom headers, request parameters, query/path variables, cookies, interceptors, encoders, and more all declaratively or through configuration.
This is essential in enterprise environments where calls may require authentication tokens, dynamic headers, correlation IDs, or different content types per endpoint.
HTTP Methods
OpenFeign supports standard HTTP methods through annotated Java interfaces. Each method maps to a specific REST operation and can be configured with URI templates, query parameters, headers, and request bodies.
Supported HTTP Methods
GET
@GetMapping
/ @RequestMapping(method = GET)
Read-only operations (e.g., fetch user, list items)
No
@GetMapping("/users/{id}") UserDto getUser(@PathVariable("id") String id);
POST
@PostMapping
Create resources, form submissions
Yes
@PostMapping("/payments") PaymentResponse create(@RequestBody PaymentRequest r);
PUT
@PutMapping
Full update of existing resource
Yes
@PutMapping("/accounts/{id}") AccountDto update(@PathVariable String id, @RequestBody AccountDto dto);
PATCH
@PatchMapping
Partial updates
Yes
@PatchMapping("/orders/{id}") OrderDto patch(@PathVariable String id, @RequestBody OrderPatch patch);
DELETE
@DeleteMapping
Delete a resource
No (usually)
@DeleteMapping("/users/{id}") void deleteUser(@PathVariable("id") String id);
HEAD
@RequestMapping(method = HEAD)
Retrieve headers or metadata only
No
@RequestMapping(value = "/files/{id}", method = RequestMethod.HEAD) ResponseEntity<?> checkFile(@PathVariable String id);
OPTIONS
@RequestMapping(method = OPTIONS)
Discover allowed methods on resource
No
@RequestMapping(value = "/files", method = RequestMethod.OPTIONS) ResponseEntity<?> options();
Example: Using All Methods in One Interface
@FeignClient(name = "user-service")
public interface UserClient {
@GetMapping("/users/{id}")
UserDto getUser(@PathVariable("id") String id);
@PostMapping("/users")
UserDto createUser(@RequestBody CreateUserRequest request);
@PutMapping("/users/{id}")
UserDto updateUser(@PathVariable("id") String id, @RequestBody UpdateUserRequest request);
@PatchMapping("/users/{id}")
UserDto patchUser(@PathVariable("id") String id, @RequestBody PatchUserRequest patch);
@DeleteMapping("/users/{id}")
void deleteUser(@PathVariable("id") String id);
@RequestMapping(value = "/users/{id}", method = RequestMethod.HEAD)
ResponseEntity<Void> checkUserExists(@PathVariable("id") String id);
@RequestMapping(value = "/users", method = RequestMethod.OPTIONS)
ResponseEntity<?> getUserOptions();
}
Setting Multiple Query Parameters
OpenFeign makes it straightforward to define multiple query parameters in method signatures. These parameters are automatically appended to the URL at runtime when the request is constructed.
This is commonly needed in search/filter/list endpoints where multiple filtering criteria, sorting options, pagination details, etc., are passed in the request.
Ways to Set Multiple Query Parameters
1. Using @RequestParam
for Each Parameter
@RequestParam
for Each ParameterBest for fixed number of parameters.
@FeignClient(name = "product-service")
public interface ProductClient {
@GetMapping("/products")
List<Product> getProducts(
@RequestParam("category") String category,
@RequestParam("sort") String sort,
@RequestParam("limit") int limit,
@RequestParam("offset") int offset
);
}
Resulting URL
GET /products?category=electronics&sort=price&limit=10&offset=0
2. Using a Map for Dynamic Query Parameters
Useful when the number or names of query parameters vary at runtime.
@FeignClient(name = "product-service")
public interface ProductClient {
@GetMapping("/products")
List<Product> getProducts(@RequestParam Map<String, String> queryParams);
}
Usage in Service Layer
Map<String, String> filters = new HashMap<>();
filters.put("category", "electronics");
filters.put("brand", "sony");
filters.put("sort", "name");
List<Product> products = productClient.getProducts(filters);
Resulting URL
GET /products?category=electronics&brand=sony&sort=name
3. Combining Fixed and Dynamic Parameters
We can use both fixed and dynamic parameters together in the same method.
@FeignClient(name = "search-service")
public interface SearchClient {
@GetMapping("/search")
List<Result> search(
@RequestParam("query") String query,
@RequestParam Map<String, String> filters
);
}
Map<String, String> extraFilters = new HashMap<>();
extraFilters.put("lang", "en");
extraFilters.put("type", "image");
List<Result> results = searchClient.search("dogs", extraFilters);
URL Example
GET /search?query=dogs&lang=en&type=image
Setting Multiple Path Variables
In REST APIs, path variables represent dynamic segments in the URI — typically used for identifying specific resources (e.g., /users/{userId}/orders/{orderId}
). OpenFeign supports this through method-level @GetMapping
or other HTTP method annotations with @PathVariable
parameters.
This is especially useful when we need to build RESTful clients that interact with resources based on hierarchical identifiers.
Defining Multiple Path Variables
1. Using Named Placeholders with @PathVariable
@PathVariable
We must ensure
The path in
@GetMapping
matches exactly with the placeholders in the method arguments.Each
@PathVariable
has a matching name (explicitly or implicitly).
@FeignClient(name = "order-service")
public interface OrderClient {
@GetMapping("/users/{userId}/orders/{orderId}")
Order getOrderByUserAndId(
@PathVariable("userId") String userId,
@PathVariable("orderId") String orderId
);
}
Resulting URL
GET /users/123/orders/456
2. Omitting Parameter Names (if method parameter names are preserved)
If our build tool preserves parameter names (e.g., via -parameters
flag in the compiler), we can omit explicit names:
@FeignClient(name = "order-service")
public interface OrderClient {
@GetMapping("/users/{userId}/orders/{orderId}")
Order getOrderByUserAndId(
@PathVariable String userId,
@PathVariable String orderId
);
}
This approach works only if our build config includes:
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
3. Combining Path and Query Parameters
We can also combine path variables and query parameters easily:
@FeignClient(name = "order-service")
public interface OrderClient {
@GetMapping("/users/{userId}/orders/{orderId}")
Order getOrderDetails(
@PathVariable("userId") String userId,
@PathVariable("orderId") String orderId,
@RequestParam("includeItems") boolean includeItems
);
}
Resulting URL
GET /users/123/orders/456?includeItems=true
Setting Request Body
In OpenFeign, we can send a request body for methods like POST, PUT, or PATCH by using the @RequestBody
annotation. The body is usually a serialized object (e.g., JSON) representing complex data such as forms, DTOs, or domain objects.
This is typically used for:
Creating new resources
Updating existing resources
Passing structured payloads
Example: POST Request with JSON Body
@FeignClient(name = "user-service")
public interface UserClient {
@PostMapping("/users")
User createUser(@RequestBody UserRequest request);
}
public class UserRequest {
private String name;
private String email;
// Getters and Setters
}
Calling the Client
UserRequest request = new UserRequest("John", "[email protected]");
User created = userClient.createUser(request);
This will serialize the UserRequest
object into a JSON body:
{
"name": "John",
"email": "[email protected]"
}
Setting Headers
Headers are a critical part of HTTP requests, conveying metadata such as:
Authentication tokens
Content types
Custom app-level flags (e.g., correlation IDs)
OpenFeign offers multiple ways to set headers for requests:
Statically via annotations
Dynamically via request interceptors
Using
@RequestHeader
parameters in method signatures
1. Static Headers with @Headers
@Headers
We can define static headers directly on the method using @Headers
from feign.Headers
.
@FeignClient(name = "order-service")
public interface OrderClient {
@Headers("Authorization: Bearer some-static-token")
@GetMapping("/orders")
List<Order> getOrders();
}
Note: Static headers are hardcoded and not suitable for dynamic tokens (e.g., JWTs).
2. Dynamic Headers with @RequestHeader
@RequestHeader
We can pass headers at runtime using method parameters.
@FeignClient(name = "order-service")
public interface OrderClient {
@GetMapping("/orders")
List<Order> getOrders(@RequestHeader("Authorization") String authHeader);
}
String token = "Bearer " + jwtProvider.getToken();
orderClient.getOrders(token);
This is useful for:
Passing authentication headers per request
Setting correlation IDs, tenant IDs, etc.
3. Custom Header Injection via Request Interceptor
For consistent headers across all requests (like JWTs or trace IDs), define a custom RequestInterceptor
.
@Configuration
public class FeignClientConfig {
@Bean
public RequestInterceptor requestInterceptor() {
return requestTemplate -> {
requestTemplate.header("Authorization", getAuthToken());
requestTemplate.header("X-Correlation-ID", UUID.randomUUID().toString());
};
}
private String getAuthToken() {
return "Bearer " + tokenProvider.getAccessToken();
}
}
Then attach it to our client:
@FeignClient(name = "order-service", configuration = FeignClientConfig.class)
public interface OrderClient {
@GetMapping("/orders")
List<Order> getOrders();
}
4. Multiple Headers Example
@FeignClient(name = "billing-service")
public interface BillingClient {
@GetMapping("/invoices")
List<Invoice> getInvoices(
@RequestHeader("Authorization") String token,
@RequestHeader("X-Tenant-ID") String tenantId,
@RequestHeader("Accept-Language") String locale
);
}
Invocation
billingClient.getInvoices(authToken, "tenant-abc", "en-US");
Adding Cookies
Cookies are often used for session management, authentication, or passing stateful information between services. While headers are more commonly used for API tokens, some systems still rely on cookies especially legacy systems or when integrating with frontend-based session handling.
In OpenFeign, cookies can be sent as part of the Cookie
HTTP header.
1. Pass Cookie via @RequestHeader("Cookie")
@RequestHeader("Cookie")
We can add cookies manually by passing them in the Cookie
header like any other header.
@FeignClient(name = "session-service")
public interface SessionClient {
@GetMapping("/session/validate")
SessionInfo validateSession(@RequestHeader("Cookie") String cookie);
}
Calling Code
String cookie = "JSESSIONID=abc123; userToken=xyz456";
SessionInfo sessionInfo = sessionClient.validateSession(cookie);
2. Use a RequestInterceptor
to Set Cookie Globally
RequestInterceptor
to Set Cookie GloballyThis is ideal when all Feign clients or specific ones need to send cookies for every request (e.g., for session stickiness or CSRF protection).
Custom Interceptor Example
@Configuration
public class FeignCookieInterceptorConfig {
@Bean
public RequestInterceptor cookieAddingInterceptor() {
return requestTemplate -> {
String jsessionId = fetchFromSessionStorage(); // Custom logic
requestTemplate.header("Cookie", "JSESSIONID=" + jsessionId);
};
}
}
Attach it to the client
@FeignClient(name = "user-service", configuration = FeignCookieInterceptorConfig.class)
public interface UserClient {
@GetMapping("/users/profile")
UserProfile getUserProfile();
}
3. Supporting Multiple Cookies
We can pass multiple cookies by separating them with semicolons (;
) in a single Cookie
header:
String cookie = "JSESSIONID=abc123; authToken=def456; lang=en";
requestTemplate.header("Cookie", cookie);
4. When we Might Need This ?
Integrating with legacy systems
Older systems relying on session cookies instead of token headers
Load-balanced sticky sessions
Routing based on JSESSIONID
Web apps requiring XSRF-TOKEN
via cookie
CSRF protection using Spring Security frontend integration
Stateless APIs behind reverse proxies
Proxies managing cookies for routing/authentication
Change content type / accept type
In OpenFeign, we can control the format of requests of send (via Content-Type
) and the format of responses we expect (via Accept
) using:
@RequestHeader
annotationsDefault Feign behavior (via
spring-cloud-starter-openfeign
)Custom configuration/interceptors for global overrides
These headers are essential for ensuring correct serialization and deserialization of payloads.
1. Content-Type
Content-Type
Specifies the media type of the request body. Common values:
application/json
application/xml
application/x-www-form-urlencoded
multipart/form-data
Set Content-Type
with @RequestMapping
Content-Type
with @RequestMapping
@FeignClient(name = "user-service")
public interface UserClient {
@PostMapping(value = "/users", consumes = "application/json")
UserResponse createUser(@RequestBody UserRequest request);
}
Explanation:
consumes = "application/json"
tells Feign and Spring to serialize the request as JSON.
2. Accept
Accept
Specifies the expected format of the response. Common values:
application/json
application/xml
text/plain
Set Accept
with produces
Attribute
Accept
with produces
Attribute@FeignClient(name = "user-service")
public interface UserClient {
@GetMapping(value = "/users/{id}", produces = "application/json")
UserResponse getUser(@PathVariable("id") Long id);
}
Explanation:
produces = "application/json"
tells the server we expect a JSON response.
3. Set Dynamically via @RequestHeader
@RequestHeader
Sometimes header values need to be dynamic or client-controlled.
@FeignClient(name = "report-service")
public interface ReportClient {
@PostMapping("/generate")
ReportResponse generateReport(
@RequestBody ReportRequest request,
@RequestHeader("Content-Type") String contentType,
@RequestHeader("Accept") String acceptType
);
}
reportClient.generateReport(
request,
"application/json",
"application/pdf"
);
4. Set Globally via RequestInterceptor
RequestInterceptor
To apply consistent content negotiation across all clients:
@Configuration
public class FeignHeaderConfig {
@Bean
public RequestInterceptor contentNegotiationInterceptor() {
return requestTemplate -> {
requestTemplate.header("Content-Type", "application/json");
requestTemplate.header("Accept", "application/json");
};
}
}
Use this in a shared config and link it in each @FeignClient(configuration = ...)
.
Last updated