Headers and Request Interceptors

Static Headers on a Method

@FeignClient(name = "payment-service", path = "/api/payments")
public interface PaymentClient {

    @PostMapping(headers = "X-Client=order-service")
    PaymentResponse charge(@RequestHeader("Idempotency-Key") String idempotencyKey,
                           @RequestBody ChargeRequest request);
}

The Idempotency-Key (for example the order ID) lets payment-service detect a repeated charge request and return the first result instead of charging twice. More on idempotency in the advance level.

Forward the Token and Correlation ID Automatically

A RequestInterceptor runs before every Feign call. This one copies the incoming Authorization and X-Correlation-Id headers to outgoing calls, so the user identity and the trace follow the request through all services:

@Configuration
class FeignHeadersConfig {

    private static final List<String> FORWARDED = List.of("Authorization", "X-Correlation-Id");

    @Bean
    RequestInterceptor forwardHeadersInterceptor() {
        return template -> {
            if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes attrs) {
                HttpServletRequest incoming = attrs.getRequest();
                for (String name : FORWARDED) {
                    String value = incoming.getHeader(name);
                    if (value != null) {
                        template.header(name, value);
                    }
                }
            }
        };
    }
}
gateway  --(Authorization, X-Correlation-Id: 3f2b...)-->  order-service
 order-service --Feign + interceptor--> product-service   (same two headers)
 order-service --Feign + interceptor--> payment-service   (same two headers)

Note: RequestContextHolder is bound to the request thread. If you call Feign from @Async methods or another thread pool, the attributes are empty; pass the values explicitly there.