skip to content

How do MVC-style annotations work on a Feign client, and what is the Contract responsible for?

level: middleimportance: should knowfreq 58%

answer

  1. Contract → MethodMetadata → RequestTemplate
  2. SpringMvcContract = MVC annotations on client
  3. explicit @PathVariable("id") names required
  4. @SpringQueryMap for POJO→query
  5. swap Contract bean for JAX-RS / native Feign

basics

~10 s

Spring Cloud OpenFeign uses a SpringMvcContract so you can annotate Feign methods with @GetMapping, @PathVariable, @RequestParam, @RequestBody, @RequestHeader — the same annotations as MVC controllers. The Contract parses these into a request template.

solid answer

~40 s

The **Contract** is the Feign component that reads a client interface's method annotations and produces `MethodMetadata` (a `RequestTemplate` blueprint: HTTP verb, URL template, query params, headers, body). Native Feign ships its own `Contract.Default` using Feign annotations like `@RequestLine`/`@Param`. Spring Cloud OpenFeign swaps in **`SpringMvcContract`** so you reuse familiar Spring MVC annotations: `@GetMapping`/`@PostMapping` for verb+path, `@PathVariable` for URI templates, `@RequestParam` for query string, `@RequestHeader` for headers, and `@RequestBody` for the payload. A class-level `@RequestMapping` or the `path` attribute prefixes all methods. Key gotcha: unlike MVC controllers, **method parameters usually need explicit annotation values** (e.g. `@PathVariable("id")`) unless you compile with `-parameters`, because Feign can't always infer names. You can override the Contract bean to accept JAX-RS or plain-Feign annotations instead.

code

java · 24 lines
java
@FeignClient(name = "orders", path = "/orders")
public interface OrderClient {

    // GET /orders/{id}?expand=items  with an Authorization header
    @GetMapping("/{id}")
    Order get(@PathVariable("id") Long id,
              @RequestParam("expand") String expand,
              @RequestHeader("Authorization") String bearer);

    // Bind many query params from a POJO
    @GetMapping("/search")
    List<Order> search(@SpringQueryMap OrderQuery query);

    @PostMapping
    Order create(@RequestBody NewOrder body);
}

// Per-client override to use native Feign annotations instead:
public class NativeFeignConfig {
    @Bean
    public feign.Contract feignContract() {
        return new feign.Contract.Default();
    }
}

go deeper

for a junior

Know that Feign methods use the same @GetMapping/@PathVariable annotations as controllers.

for a middle

Explain SpringMvcContract, the explicit-parameter-name rule, and @SpringQueryMap.

for a senior

Describe how the Contract produces MethodMetadata and how to swap it for JAX-RS/native contracts.

for a principal

Reason about sharing interfaces between client and server, migration strategy, and contract-bean scoping.

## The Contract abstraction In Feign, a **`Contract`** is the strategy that inspects each method of the client interface and translates its annotations into **`MethodMetadata`** — an internal description of how to build the HTTP call (verb, URL template, query parameters, header templates, and which argument is the body). Feign then uses that metadata plus an **Encoder**, **Decoder**, and **RequestInterceptor**s to produce a concrete `RequestTemplate` at call time. ### Native Feign vs Spring MVC contract - **`Contract.Default`** (native Feign) understands Feign's own annotations: `@RequestLine("GET /products/{id}")`, `@Param("id")`, `@Headers`, `@Body`. - **`SpringMvcContract`** — installed automatically by Spring Cloud OpenFeign — understands **Spring MVC annotations**, so the same vocabulary you use on `@RestController` methods works on the client: - `@GetMapping` / `@PostMapping` / `@PutMapping` / `@DeleteMapping` / `@RequestMapping` → HTTP verb and path template. - `@PathVariable` → substitutes `{...}` placeholders in the path. - `@RequestParam` → appends query-string parameters (or form params for form content types). - `@RequestHeader` → sets request headers from arguments. - `@RequestBody` → marks the argument serialized into the request body (via the Encoder). - Class-level `@RequestMapping`/`path` prefixes every method path. ## The parameter-name gotcha Spring MVC controllers can often omit the annotation value (`@PathVariable Long id`) because the servlet stack and `-parameters` compilation expose parameter names. On Feign interfaces this inference is unreliable — historically Feign requires **explicit names**: `@PathVariable("id") Long id`, `@RequestParam("page") int page`. Omitting them can throw `IllegalStateException: PathVariable annotation was empty on param 0` at proxy-build time. Compiling with the `-parameters` javac flag improves inference, but explicit names remain the safe habit. ## Other differences from server-side MVC - **No `@ModelAttribute`/no view resolution** — it's a client, not a controller. - **`@SpringQueryMap`** — bind a POJO/Map to multiple query parameters (there's no automatic object-to-query binding otherwise). - **Multipart** — supported via a `feign-form` encoder and `@RequestPart`. - **Collection query params** expand into repeated params. ## Overriding the Contract Because the Contract is just a bean, you can replace it. Declaring a `feign.Contract` bean (globally, or per client via the `configuration` class) of type `Contract.Default` makes Feign expect native `@RequestLine` annotations; adding `feign-jaxrs` lets you use JAX-RS annotations via `JAXRSContract`. This is how you migrate legacy Feign interfaces or share interfaces with a JAX-RS server. ## When it matters Understanding the Contract explains *why* MVC annotations work on a client, why parameter names must be explicit, and how to plug in alternative annotation styles — all common interview and debugging scenarios.

  • Why do Feign method parameters often need explicit annotation values like @PathVariable("id") when MVC controllers don't?
    Feign's SpringMvcContract can't reliably read parameter names from bytecode unless compiled with the `-parameters` flag, so it demands explicit names in the annotation. MVC controllers benefit from the servlet infrastructure and parameter-name discovery, so they can infer them. Without explicit names, proxy construction fails.
  • How would you bind a whole POJO to query parameters on a Feign method?
    Use `@SpringQueryMap` on a POJO or Map parameter; its properties/entries are expanded into individual query-string parameters. Plain `@RequestParam` binds one parameter at a time, and `@RequestBody` would put the object in the body, not the query string.

saying these in an interview costs you the question

  • Claiming Feign uses JAX-RS annotations by default in Spring Cloud
  • Thinking you can always omit @PathVariable/@RequestParam names like in controllers
  • Believing a plain POJO parameter auto-maps to query params without @SpringQueryMap
  • Confusing the Contract (annotation parsing) with the Encoder/Decoder (body serialization)

context