skip to content

Content Negotiation

Spring picks a response media type from the Accept header, the produces attribute, and optionally a path extension or format parameter, returning 406 or 415 when nothing matches. Interviewers use those two status codes to test whether you understand negotiation.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

5

What is content negotiation in Spring MVC, and how does Spring decide what format (JSON, XML) to send back?

level: juniorimportance: must knowfreq 62%

answer

  1. Accept header in, Content-Type out
  2. ContentNegotiationManager resolves media types
  3. HttpMessageConverters produce them
  4. 406 when nothing acceptable
  5. produces narrows what a method emits

basics

~20 s

Content negotiation is how Spring picks the response format. The client sends an Accept header (e.g. application/json); Spring matches it against the formats a handler can produce and the available message converters, then writes that format.

solid answer

~40 s

Content negotiation is the process of selecting the response media type based on what the client wants and what the server can produce. The client expresses its preference primarily through the HTTP Accept header (e.g. `Accept: application/json`). For a `@ResponseBody`/`@RestController` method, Spring's ContentNegotiationManager resolves the requested media types, then RequestResponseBodyMethodProcessor walks the registered HttpMessageConverters to find one that can serialize the return object into an acceptable type. If a converter matches (e.g. Jackson for JSON), it writes that format and sets Content-Type. A handler can also declare `produces = "application/json"` on `@RequestMapping`/`@GetMapping` to restrict which types it emits. If nothing the server can produce satisfies the Accept header, Spring returns 406 Not Acceptable.

code

java · 11 lines
java
@RestController
public class ProductController {

    // Returns JSON when the client sends Accept: application/json.
    // 'produces' restricts this method to JSON; a request asking only
    // for application/xml would get 406 Not Acceptable.
    @GetMapping(value = "/products/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
    public Product getProduct(@PathVariable long id) {
        return new Product(id, "Widget");
    }
}

go deeper

for a junior

Know that the Accept header requests a format and Spring answers with a matching Content-Type; JSON is the common default.

for a middle

Explain the manager + converter interplay and the role of the produces attribute.

for a senior

Discuss the resolveMediaTypes flow, q-values, and the 406/415 distinction precisely.

for a principal

Reason about default fallbacks, multi-format APIs, and when to override negotiation defaults for API contract stability.

**Content negotiation** is the mechanism by which an HTTP server and client agree on the *representation* (format) of a resource — most commonly JSON vs XML. In Spring MVC it answers the question: 'The controller method returns a `Product` object; should I serialize it as JSON, XML, or something else, and how do I choose?' **The two inputs to the decision:** 1. **What the client wants** — expressed via the request's `Accept` header, e.g. `Accept: application/json` or `Accept: application/xml, application/json;q=0.9`. The `q` value (0–1) is a *quality* weight expressing relative preference. 2. **What the server can produce** — the set of registered `HttpMessageConverter`s (a `MappingJackson2HttpMessageConverter` for JSON, `Jaxb2RootElementHttpMessageConverter`/`MappingJackson2XmlHttpMessageConverter` for XML, `StringHttpMessageConverter`, etc.), optionally narrowed by a `produces` attribute on the mapping. **The flow for a body-returning method:** - The handler method returns an object (via `@ResponseBody` or being inside a `@RestController`). - `RequestResponseBodyMethodProcessor` (a `HandlerMethodReturnValueHandler`) is invoked. - It asks the **`ContentNegotiationManager`** to `resolveMediaTypes(request)` — this returns the list of acceptable media types, sorted by preference. - It computes the *producible* types: for each converter that `canWrite` the return type, its supported media types (intersected with `produces` if declared). - It finds the best match between acceptable and producible types. - The winning converter serializes the object and sets the `Content-Type` response header. **Key annotations:** - `produces` — on `@GetMapping(produces = "application/json")`: declares the media type(s) this method emits; participates in negotiation and also acts as a mapping condition. - `consumes` — the mirror for *request* bodies: it matches the incoming `Content-Type`. **Failure outcomes:** - **406 Not Acceptable** (`HttpMediaTypeNotAcceptableException`): the server cannot produce anything the client's `Accept` header allows. - **415 Unsupported Media Type** (`HttpMediaTypeNotSupportedException`): the client sent a request body whose `Content-Type` no `consumes`/converter accepts. **When to care:** Almost every REST API relies on this implicitly — Jackson-for-JSON 'just works' because Spring Boot auto-registers the converter and the default Accept negotiation. You reach for explicit configuration when you support multiple formats, want a `?format=json` query parameter, or need a sensible default when the client sends no Accept header.

  • If the client sends no Accept header at all, what happens?
    By default it is treated as `Accept: */*`, so Spring picks the first producible type (typically JSON if Jackson is on the classpath). You can override the fallback with `ContentNegotiationConfigurer.defaultContentType(...)`.
  • Where do the JSON/XML converters come from in Spring Boot?
    Boot auto-registers `HttpMessageConverter`s based on the classpath — `MappingJackson2HttpMessageConverter` when Jackson is present, XML converters when Jackson-XML or JAXB is present — via `HttpMessageConvertersAutoConfiguration`.

saying these in an interview costs you the question

  • Thinking the Content-Type of the *request* decides the *response* format (that's Accept's job; Content-Type describes the request body).
  • Believing the file extension in the URL (.json) still drives negotiation by default — it does not in modern Spring.
  • Confusing 406 (can't produce what client wants) with 415 (can't read what client sent).

context

open as a page

Explain produces vs consumes on a request mapping, and which HTTP status each drives when it fails to match.

level: middleimportance: must knowfreq 70%

basics

~10 s

consumes matches the request body's Content-Type; a mismatch yields 415 Unsupported Media Type. produces matches the client's Accept header (what the response can be); a mismatch yields 406 Not Acceptable.

open as a page

Walk through how Spring resolves the exact response type when the Accept header lists several media types with q-values and the server has multiple converters.

level: seniorimportance: should knowfreq 40%

basics

~20 s

Spring parses Accept into media types sorted by q-value and specificity, computes the types the converters (and any produces) can write, then intersects the two lists and picks the most specific, highest-priority match. If the intersection is empty it returns 406.

open as a page

How does ContentNegotiationManager work, what ContentNegotiationStrategy implementations exist, and how do you configure them?

level: seniorimportance: should knowfreq 52%

basics

~20 s

ContentNegotiationManager holds an ordered list of ContentNegotiationStrategy objects and calls each to resolve requested media types. Built-in strategies read the Accept header, a query parameter (?format=json), or a fixed default. You configure it via WebMvcConfigurer.configureContentNegotiation.

open as a page

Why did Spring deprecate and disable path-extension (suffix) content negotiation by default, and how should APIs handle format selection now?

level: principalimportance: nice to knowfreq 26%

basics

~20 s

URL suffixes like /data.json for negotiation caused security problems (Reflected File Download, content sniffing) and routing ambiguity. Since Spring 5.3 path-extension negotiation is off by default. Prefer the Accept header, or an explicit query parameter, for format selection.

open as a page