skip to content

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%

answer

  1. /report.json suffix negotiation — now off by default (5.3)
  2. RFD attack + content sniffing = security reason
  3. dot in path variable = routing ambiguity
  4. Prefer Accept header; opt-in ?format= parameter
  5. PathPatternParser has no suffix matching

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.

solid answer

~40 s

Historically Spring could infer the response media type from a URL suffix (`/report.json` vs `/report.xml`) via `PathExtensionContentNegotiationStrategy`. This was disabled by default in Spring Framework 5.3 (`favorPathExtension = false`) and deprecated because: it enabled **Reflected File Download (RFD)** attacks and content-type sniffing; suffixes collided with legitimate dots in path variables; and `useSuffixPatternMatch` in path matching created ambiguous routing. The modern recommendation is to rely on the **`Accept` header** (`HeaderContentNegotiationStrategy`) as the primary, RFC-standard mechanism, and, where header control isn't possible (browser links), enable an explicit **query parameter** via `favorParameter(true).parameterName("format")` with registered `mediaType` mappings. This keeps URLs stable and avoids the security/routing pitfalls of extensions.

code

java · 18 lines
java
@Configuration
public class NegotiationConfig implements WebMvcConfigurer {

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer
            // Header-based negotiation is the primary, standard mechanism (default on).
            .ignoreAcceptHeader(false)
            // Safe, explicit alternative for browsers/links: /report?format=xml
            .favorParameter(true)
            .parameterName("format")
            .mediaType("json", MediaType.APPLICATION_JSON)
            .mediaType("xml", MediaType.APPLICATION_XML)
            // Do NOT re-enable this for new APIs (RFD / routing ambiguity):
            // .favorPathExtension(true)
            .defaultContentType(MediaType.APPLICATION_JSON);
    }
}

go deeper

for a junior

Know that /data.json style format selection is old and Accept header is preferred now.

for a middle

State that path-extension negotiation is disabled by default since Spring 5.3 and name the query-parameter alternative.

for a senior

Explain the security (RFD/sniffing) and routing-ambiguity reasons and configure header + parameter negotiation.

for a principal

Frame format selection as an API-contract and security decision; handle legacy suffixes via explicit mappings + hardening and caching via Vary.

**What path-extension negotiation was.** Early Spring MVC let a URL *suffix* select the representation: `GET /accounts/42.json` → JSON, `GET /accounts/42.xml` → XML, implemented by `PathExtensionContentNegotiationStrategy` (and its servlet variant). It was convenient — you could get a format by typing a URL, no header needed — and paired with `useSuffixPatternMatch` in `RequestMappingHandlerMapping`, which let `/foo` also match `/foo.*`. **Why it was deprecated/disabled (Spring 5.3, ~2020):** 1. **Reflected File Download (RFD) attacks.** If a user-influenced value is reflected in a JSON response and the URL ends in an attacker-chosen extension like `.bat`/`.cmd`, some browsers offer to *download and execute* the response as that file type. Suffix-driven negotiation widened this surface. Disabling extension handling closes it. 2. **Content-type / MIME sniffing risks.** Serving the same resource under many extensions increased the chance a browser mis-sniffs content. 3. **Routing ambiguity.** A dot is legal inside path variables (`/users/john.doe`); suffix matching made Spring guess whether `.doe` was an extension or part of the value, causing 404s or wrong media types. `useSuffixPatternMatch` was deprecated for the same reason. 4. **Redundancy.** The `Accept` header is the RFC-7231 standard signal and works for every client that matters for APIs. **Current defaults.** In Spring 5.3+ (and Spring Boot 2.4+), `favorPathExtension` defaults to `false`; the path-extension strategies are deprecated. `PathPatternParser` (the default matcher in Spring 6 / Boot 3) does not do suffix pattern matching at all. So out of the box, only the `Accept` header (and any parameter/custom strategies you enable) drive negotiation. **Recommended approaches now:** - **Primary: `Accept` header.** Standard, cache-friendly (with `Vary: Accept`), and the default. APIs should document required Accept values. - **Optional: explicit query parameter.** For links/browsers that can't set headers, enable `favorParameter(true).parameterName("format").mediaType("json", APPLICATION_JSON).mediaType("xml", APPLICATION_XML)`. This is explicit and safe — it doesn't touch the path, so no routing ambiguity or RFD-via-extension. - **Versioning:** use a vendor media type (`application/vnd.myapi.v2+json`) negotiated via Accept and (optionally) a custom `ContentNegotiationStrategy`, not a URL suffix. - **Caching:** set `Vary: Accept` (or `Vary: format`) so intermediaries cache per representation. **If you must re-enable extensions** (legacy clients): you can call `favorPathExtension(true)` and register `mediaType` mappings, but scope it tightly and pair it with `Content-Disposition`/`X-Content-Type-Options: nosniff` hardening. Generally avoid it. **Architectural takeaway (principal lens):** format selection is part of your API *contract* and *security posture*. Prefer the standard, header-based mechanism; expose a controlled parameter only for real UX needs; never let arbitrary URL suffixes influence media type or dispatch. This keeps the URL space clean, avoids a known attack class, and plays well with the `PathPatternParser` default.

  • A legacy client depends on /export.csv suffixes. How do you support it without reopening the RFD risk broadly?
    Prefer routing those to explicit, dedicated endpoints (e.g. `@GetMapping("/export.csv", produces = "text/csv")`) rather than global path-extension negotiation, and add `X-Content-Type-Options: nosniff` plus `Content-Disposition` headers. Keep the suffix a literal mapping, not a negotiation signal.
  • What HTTP response header should accompany content-negotiated responses for correct caching?
    `Vary: Accept` (and `Vary: <param>` if you use a query parameter), so caches key each stored representation by the negotiation input and don't serve JSON to a client that asked for XML.

saying these in an interview costs you the question

  • Recommending URL suffixes (.json/.xml) as the modern default way to negotiate.
  • Being unaware of RFD as the security driver behind disabling path-extension negotiation.
  • Thinking Spring 6 / PathPatternParser still supports suffix pattern matching.

context