How does content negotiation work in Spring WebFlux, and how do you customize it via WebFluxConfigurer?
answer
- RequestedContentTypeResolver (header by default)
- configureContentTypeResolver(RequestedContentTypeResolverBuilder)
- No path-extension in WebFlux
- parameterResolver ?format=json, defaultContentType fallback
- No matching writer -> 406; produces is a hard constraint
basics
~20 sWebFlux picks the response media type using a RequestedContentTypeResolver, which by default reads the Accept header. You customize it by overriding configureContentTypeResolver(RequestedContentTypeResolverBuilder) — e.g. enable a query parameter strategy or set a default media type.
solid answer
~50 sContent negotiation is how WebFlux decides which media type to produce for a response. It's driven by a RequestedContentTypeResolver, and unlike MVC, WebFlux defaults to header-based negotiation only (the Accept header) — there is no path-extension strategy. You customize it by overriding WebFluxConfigurer.configureContentTypeResolver(RequestedContentTypeResolverBuilder builder). The builder lets you add strategies: parameterResolver() to negotiate via a query parameter like ?format=json, fixedResolver()/defaultContentType() to fall back to a default media type when the client sends nothing usable, and header resolution (default). The resolved media type is matched against the codecs' supported types and the controller's produces attribute to select the writer. Common use: expose a ?format param for browsers, or set a sensible default (e.g. application/json) so requests without an Accept header still serialize correctly. Note the produces attribute on @RequestMapping still constrains what a handler will emit.
code
java · 14 linesimport org.springframework.http.MediaType;
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
import org.springframework.web.reactive.config.WebFluxConfigurer;
@Configuration
public class NegotiationConfig implements WebFluxConfigurer {
@Override
public void configureContentTypeResolver(RequestedContentTypeResolverBuilder builder) {
builder.parameterResolver() // ?format=json / ?format=xml
.mediaType("json", MediaType.APPLICATION_JSON)
.mediaType("xml", MediaType.APPLICATION_XML);
// header (Accept) resolution stays active as the default strategy
}
}go deeper
Know negotiation uses the Accept header and is tuned via configureContentTypeResolver.
Know how to enable the ?format parameter strategy and set a default media type.
Explain the resolver-builder strategies, the no-path-extension difference from MVC, and how produces + codecs yield 406.
Design negotiation policy across API and browser clients, ensure required writers exist, and reason about default media types and versioning implications.
## What content negotiation is When a controller returns an object, the framework must choose a **media type** (e.g. `application/json`, `application/xml`) and the matching codec/writer. 'Content negotiation' is that decision. On the reactive stack it is performed by a `RequestedContentTypeResolver` (composed of one or more strategies), yielding a prioritized list of acceptable media types from the request. ## WebFlux defaults differ from MVC - **WebFlux default: header strategy only.** It reads the `Accept` header. There is **no path-extension** strategy (MVC historically had `favorPathExtension`, now deprecated/removed; WebFlux never shipped it). This matters if you migrate from MVC expecting `/data.json` to negotiate — it won't. - If the `Accept` header is absent or `*/*`, without further config the resolver yields all media types and the first compatible codec wins (typically JSON in a Boot app). ## Customizing via the callback ```java @Override public void configureContentTypeResolver(RequestedContentTypeResolverBuilder builder) { builder.parameterResolver().parameterName("format") // ?format=json|xml .mediaType("json", MediaType.APPLICATION_JSON) .mediaType("xml", MediaType.APPLICATION_XML); // builder also supports header (default) and fixed/default fallbacks } ``` `RequestedContentTypeResolverBuilder` composes strategies in priority order: - **headerResolver** — the default, reads `Accept`. - **parameterResolver()** — negotiates via a query param (default name `format`), useful for browsers that can't set `Accept` easily; you map short keys (`json`) to `MediaType`s. - **fixedResolver(MediaType...)** — always returns a fixed list. - A **default content type** can be set so requests with no usable `Accept` still resolve to a chosen type. ## How the choice becomes a response The resolved acceptable media types are intersected with (a) the media types the response codecs (`ServerCodecConfigurer`) can write and (b) the handler's `produces` attribute on `@RequestMapping`/`@GetMapping`. The best match selects the `HttpMessageWriter`. If nothing matches, the client gets **406 Not Acceptable**. ## Gotchas - Adding an XML strategy requires an XML codec on the classpath (e.g. Jackson XML); negotiation alone won't produce XML without a writer for it. - `produces` on the handler is a hard constraint — a handler declared `produces = "application/json"` will never emit XML even if the client asks for it (it just won't match / 406). - No path-extension negotiation — don't rely on file suffixes. - The parameter strategy is opt-in; it's off by default, unlike some MVC setups. - Content negotiation only chooses the writer; the actual serialization is the codec's job (codec internals are a sibling topic). ## When to use - Enable `parameterResolver` for human-facing endpoints where clients can't set `Accept`. - Set a default media type for robust behaviour when clients omit `Accept`. - Otherwise the header default is usually correct for API clients.
- How does WebFlux content negotiation differ from Spring MVC's?WebFlux negotiates via the Accept header by default and offers a parameter strategy, but it has NO path-extension strategy at all. MVC historically supported favorPathExtension (matching /data.json), which is deprecated there and simply absent in WebFlux — so file-suffix negotiation won't work reactively.
- A client sends Accept: application/xml but gets 406. What are the likely causes?Either no XML HttpMessageWriter/codec is registered (no Jackson XML on the classpath), or the target handler declares produces = application/json, which is a hard constraint that excludes XML. Fix by adding an XML codec and/or relaxing the produces attribute.
saying these in an interview costs you the question
- Claiming WebFlux supports path-extension content negotiation like older MVC
- Thinking enabling XML negotiation is enough without an XML codec on the classpath
- Believing content negotiation overrides a handler's produces attribute