skip to content

How do you apply configuration (encoders, interceptors, log level) to one specific Feign client without affecting all of them?

level: seniorimportance: should knowfreq 45%

answer

  1. each client = own child context
  2. configuration = XConfig.class targets one
  3. NEVER @Configuration in scanned package (leaks global)
  4. defaultConfiguration for all clients
  5. properties override @Bean by default

basics

~20 s

Use @FeignClient(configuration = MyConfig.class). Each client gets its own child application context, so beans in that config apply only to that client. Critically, don't put @Configuration + component-scan on it, or it leaks to everyone.

solid answer

~40 s

Each Feign client runs in its own isolated child application context. You target one client with @FeignClient(name = "x", configuration = XConfig.class); beans defined there (Encoder, Decoder, RequestInterceptor, Logger.Level, ErrorDecoder, Contract, etc.) override the defaults for that client only. The big gotcha: the config class must NOT be picked up by your main @ComponentScan — if it's annotated @Configuration and sits in a scanned package, it becomes a global default for every Feign client, defeating the isolation. Keep it outside the scan path or don't annotate it @Configuration. To set global defaults for all clients, use @EnableFeignClients(defaultConfiguration = ...). Property-based config (spring.cloud.openfeign.client.config.<name>...) is the non-code alternative and, by default, properties win over the @Configuration beans.

code

java · 19 lines
java
// NOTE: no @Configuration, and kept OUT of the main component-scan package
public class OrderFeignConfig {
    @Bean
    Logger.Level orderLogLevel() { return Logger.Level.FULL; }

    @Bean
    RequestInterceptor authHeader() {
        return template -> template.header("X-Api-Key", "secret");
    }

    @Bean
    ErrorDecoder orderErrorDecoder() { return new OrderErrorDecoder(); }
}

@FeignClient(name = "order-service", configuration = OrderFeignConfig.class)
interface OrderClient {
    @GetMapping("/orders/{id}")
    OrderDto get(@PathVariable long id);
}

go deeper

for a junior

Know the configuration attribute scopes settings to one client.

for a middle

Name the configurable beans (RequestInterceptor, Logger.Level, ErrorDecoder) and the child-context idea.

for a senior

Explain the @Configuration-leak gotcha and default vs per-client precedence.

for a principal

Reason about property-vs-code precedence, defaultConfiguration governance, and contextId-scoped config for shared service ids.

**Isolated child contexts.** Spring Cloud gives **each** Feign client its own **child `ApplicationContext`** (a 'named context'), keyed by the client's `name`/`contextId`. Beans in that child context configure only that client. This is the same named-context mechanism SCLB uses per service. **Per-client configuration.** Pass a config class: `@FeignClient(name = "order-service", configuration = OrderFeignConfig.class)`. Inside it you declare `@Bean`s that Feign recognizes: - `Encoder` / `Decoder` — body serialization. - `Contract` — annotation dialect (e.g. use JAX-RS instead of Spring MVC). - `RequestInterceptor` — mutate every outgoing request (add auth headers, correlation ids). - `Logger.Level` — `NONE`/`BASIC`/`HEADERS`/`FULL` (also needs the client's logger at DEBUG to emit). - `ErrorDecoder` — map non-2xx responses to exceptions. - `Request.Options` — connect/read timeouts (though properties are usually cleaner). - `Retryer` — Feign-level retry policy. **THE gotcha — don't let it leak.** The config class **must not be reachable by your application's `@ComponentScan`**. If it carries `@Configuration` and lives in a scanned package, Spring registers its beans in the **parent** context too, making them **global defaults for every Feign client**. The docs explicitly warn to place such classes in a non-overlapping package, or omit `@Configuration`. Symptom: an interceptor meant for one client mysteriously fires on all of them. **Global defaults.** To intentionally configure all clients, use `@EnableFeignClients(defaultConfiguration = GlobalFeignConfig.class)`. Per-client `configuration` then overrides those defaults for that client. **Property-based configuration.** You can skip code entirely: `spring.cloud.openfeign.client.config.order-service.loggerLevel=full`, `...requestInterceptors[0]=...`, etc. The special client name `default` sets defaults for all. **Precedence:** by default, **properties override `@Configuration` beans**; you can flip that with `spring.cloud.openfeign.client.config.default.default-to-properties=false` so Java config wins. **contextId reuse.** Per-client config is resolved by the context id, so if two clients share a `name`, give them distinct `contextId`s to configure them independently. **Older property prefix.** Legacy code uses `feign.client.config.<name>...`; modern Spring Cloud uses `spring.cloud.openfeign.client.config.<name>...`. Prefer the new prefix.

  • You added @Configuration to your per-client config class and now the interceptor runs for every Feign client. Why?
    Because @Configuration in a component-scanned package registers those beans in the parent context as global Feign defaults. Move the class outside the scan path or drop @Configuration so it's only used when referenced via the configuration attribute.
  • If both a @Bean and a property set the log level for a client, which wins?
    By default properties win (default-to-properties=true). Set spring.cloud.openfeign.client.config.default.default-to-properties=false to make Java @Configuration take precedence.

saying these in an interview costs you the question

  • Putting @Configuration on the per-client config class inside the scanned package
  • Thinking configuration = X.class merges into a global context
  • Assuming @Bean always beats properties (it's the opposite by default)

context