🌐 Section 6 · Question #17

What is Accept header

The Accept request HTTP header (standardized in RFC 9110, Section 12.5.1) is used by a client to announce which data formats (MIME types / Media Types) it expects and is capable...


🟢 Junior Level

The Accept request HTTP header (standardized in RFC 9110, Section 12.5.1) is used by a client to announce which data formats (MIME types / Media Types) it expects and is capable of processing in the response body.

How It Works

In REST architecture, a single conceptual resource (such as a User profile, Invoice, or Metrics report) can have multiple representations: JSON, XML, CSV, or PDF. The Accept header enables the client to negotiate the most appropriate representation dynamically:

GET /api/v1/users/42 HTTP/1.1
Host: api.example.com
Accept: application/json

The server inspects the requested media type, serializes the domain object to JSON, and responds with:

HTTP/1.1 200 OK
Content-Type: application/json

{"id": 42, "name": "Ivan", "role": "ENGINEER"}

Key Difference Between Accept and Content-Type

Header Role Message Type Example
Accept Informs the server what format the client wants in return Request only Accept: application/json
Content-Type Informs the recipient what format is currently being transmitted in the body Request and Response Content-Type: application/json
  ┌────────────────────────────────────────────────────────┬────────────────────────────────────────────────────────┐
  │                   Content-Type                         │                        Accept                          │
  ├────────────────────────────────────────────────────────┼────────────────────────────────────────────────────────┤
  │ • Declares: "Here is what I am SENDING right now"      │ • Declares: "Here is what I am EXPECTING in return"    │
  │ • Present on requests and responses WITH a body        │ • Present on requests asking for a representation body │
  │ • Example: `Content-Type: application/json`            │ • Example: `Accept: application/json`                  │
  └────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────┘

What Happens When a Format Is Unsupported

If a client sends Accept: application/xml, but the server’s endpoint is only configured to produce application/json, the server cannot fulfill the client’s explicit representation contract.

Per RFC 9110, the server MUST reject the request with HTTP status code 406 Not Acceptable.


🟡 Middle Level

Server-Driven Content Negotiation Mechanics

The process whereby the server chooses the best response representation matching the client’s preferences is known as Server-Driven Content Negotiation:

Client (Accept: application/json;q=0.9, application/xml;q=0.5)
                            │
                            ▼
               Spring DispatcherServlet
                            │
                            ▼
               ContentNegotiationManager
       (Evaluates supported controller MediaTypes)
                            │
                            ▼
           Handler: @GetMapping(produces = {JSON, XML})
                            │
                 Selects JSON (q=0.9 > q=0.5)
                            │
                            ▼
       MappingJackson2HttpMessageConverter.canWrite() == true
                            │
                            ▼
         HTTP 200 OK (Content-Type: application/json)
                     (Vary: Accept)

Quality Values (q-values) & Graceful Degradation

The client can supply a comma-separated list of acceptable media types, each accompanied by an optional weight or quality factor (q-value) ranging from 0.0 (unacceptable) to 1.0 (maximum preference, default if omitted):

Accept: application/json;q=1.0, application/xml;q=0.8, text/plain;q=0.2, */*;q=0.1
  • Order of Preference: The server evaluates supported serializers against the client’s weights. If both JSON and XML are available, it selects JSON (q=1.0).
  • Graceful Degradation: If JSON serialization is unavailable or disabled for a specific payload, the server falls back to XML (q=0.8), then plain text (q=0.2).
  • Wildcard Catch-All (*/*): Specifying */*;q=0.1 signals: “If none of my preferred formats are available, deliver any format rather than failing with a 406 Not Acceptable error.”

Media Type Specificity (Precedence Rules)

When two media types have identical q-values, RFC 9110 dictates that the more specific media type takes precedence:

  1. Vendor / Subtype Extension: application/vnd.company.user.v1+json (highest specificity)
  2. Specific Type & Subtype: application/json
  3. Wildcard Subtype: application/* (any application payload)
  4. Complete Wildcard: */* (lowest specificity)

Caching and the Mandatory Vary: Accept Header (RFC 9111)

When a server can serve different payload representations from the exact same URI, it MUST include the Vary: Accept response header:

HTTP/1.1 200 OK
Content-Type: application/json
Vary: Accept

Why Vary: Accept Is Critical

Intermediate caching layers (CDNs such as Cloudflare or Fastly, and reverse proxies like NGINX) use the request URI as the default primary cache key (GET /api/v1/reports/42).

  Client 1 (Requests XML)  ──> GET /reports/42 (Accept: application/xml)  ──> [CDN Cache] (Stores XML)
  Client 2 (Requests JSON) ──> GET /reports/42 (Accept: application/json) ──> [CDN Cache] (BUG: Returns XML!)

Without Vary: Accept:

  1. Client 1 requests XML; the CDN fetches XML from the origin and caches it under /reports/42.
  2. Client 2 requests JSON from the same URI; the CDN matches the cache key and serves the cached XML payload to a client that cannot parse XML, triggering frontend breakage!

With Vary: Accept: The CDN incorporates the client’s Accept header value into its composite cache key (Hash(URI + Accept)), maintaining isolated cache slots for JSON, XML, and CSV.


🔴 Senior Level

The Content Negotiation Header Family

RFC 9110 specifies four orthogonal dimensions of HTTP content negotiation:

Request Header Response Header Negotiated Dimension Mismatch Outcome
Accept Content-Type Data serialization format & schema 406 Not Acceptable
Accept-Encoding Content-Encoding Payload compression algorithm (gzip, br, zstd, deflate) 406 or identity (uncompressed fallback)
Accept-Language Content-Language Natural language / localization (en-US, de-DE, ru-RU) Server fallback to default locale
Accept-Charset Content-Type; charset= Character encoding (Legacy; UTF-8 is now universal standard) 406 Not Acceptable

Production Content Negotiation in Spring Boot 3

Modern REST architectures deprecate legacy path extensions (/users/1.json) and query parameters (/users/1?format=json) because they pollute resource identifiers and create security vectors (such as RFD — Reflected File Download attacks).

Spring Boot 3 strictly enforces header-based negotiation via WebMvcConfigurer:

package com.example.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration(proxyBeanMethods = false)
public class WebMvcContentNegotiationConfig implements WebMvcConfigurer {

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer
            // 1. Disable legacy URI path extensions (/api/reports/42.json is rejected)
            .favorPathExtension(false)
            // 2. Disable query parameter overrides (/api/reports/42?format=json ignored)
            .favorParameter(false)
            // 3. Enable standard HTTP Accept header evaluation
            .ignoreAcceptHeader(false)
            // 4. Fallback default if client sends no Accept header or sends */*
            .defaultContentType(MediaType.APPLICATION_JSON)
            // 5. Explicitly declare recognized MediaTypes
            .mediaType("json", MediaType.APPLICATION_JSON)
            .mediaType("xml", MediaType.APPLICATION_XML)
            .mediaType("csv", MediaType.parseMediaType("text/csv"));
    }
}

Multi-Representation Controller with RFC 9457 ProblemDetail Handling

Below is a production Spring Boot 3 controller serving JSON and CSV representations of the same resource, ensuring strict adherence to Vary: Accept and RFC 9457 ProblemDetail on media type rejection:

package com.example.controller;

import com.example.dto.ReportDto;
import com.example.service.ReportExportService;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ProblemDetail;
import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.List;

@RestController
@RequestMapping("/api/v1/reports")
public class ReportExportController {

    private final ReportExportService reportService;

    public ReportExportController(ReportExportService reportService) {
        this.reportService = reportService;
    }

    // Representation 1: JSON payload
    @GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<ReportDto> getReportAsJson(@PathVariable Long id) {
        ReportDto report = reportService.findById(id);
        return ResponseEntity.ok()
                .header(HttpHeaders.VARY, HttpHeaders.ACCEPT)
                .body(report);
    }

    // Representation 2: CSV streaming export (same URI, different Accept)
    @GetMapping(value = "/{id}", produces = "text/csv")
    public ResponseEntity<byte[]> getReportAsCsv(@PathVariable Long id) {
        String csvData = reportService.generateCsv(id);
        byte[] csvBytes = csvData.getBytes(StandardCharsets.UTF_8);

        return ResponseEntity.ok()
                .header(HttpHeaders.VARY, HttpHeaders.ACCEPT)
                .contentType(MediaType.parseMediaType("text/csv; charset=utf-8"))
                .body(csvBytes);
    }

    // RFC 9457 ProblemDetail handler for 406 Not Acceptable
    @ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
    public ResponseEntity<ProblemDetail> handleNotAcceptable(HttpMediaTypeNotAcceptableException ex) {
        ProblemDetail problem = ProblemDetail.forStatusAndDetail(
                HttpStatus.NOT_ACCEPTABLE,
                "Requested representation cannot be generated by the server."
        );
        problem.setType(URI.create("https://api.example.com/errors/not-acceptable"));
        problem.setTitle("Not Acceptable");
        problem.setProperty("supportedMediaTypes", List.of(
                MediaType.APPLICATION_JSON_VALUE,
                "text/csv"
        ));

        // RFC 9457 responses should be returned as application/problem+json
        return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE)
                .contentType(MediaType.APPLICATION_PROBLEM_JSON)
                .body(problem);
    }
}

Media Type Versioning with Vendor MIME Types

The Accept header provides the purest mechanism for REST API versioning by keeping URIs clean and immutable while decoupling the resource identifier from its evolutionary representation:

GET /api/users/42 HTTP/1.1
Host: api.example.com
Accept: application/vnd.company.user.v2+json
@RestController
@RequestMapping("/api/users")
public class UserVersioningController {

    @GetMapping(value = "/{id}", produces = "application/vnd.company.user.v1+json")
    public UserV1Dto getUserV1(@PathVariable Long id) {
        return userService.findV1(id);
    }

    @GetMapping(value = "/{id}", produces = "application/vnd.company.user.v2+json")
    public UserV2Dto getUserV2(@PathVariable Long id) {
        return userService.findV2(id);
    }
}

4 Tricky Questions

1. Which HTTP status code must the server return if a client requests Accept: text/csv but the controller only supports application/json?

Answer:
The server must return 406 Not Acceptable.
Returning 400 Bad Request or 404 Not Found violates RFC 9110 Section 15.5.7. The request itself is syntactically valid and the resource exists, but no available representation matches the client’s requested media types. In Spring MVC, this triggers HttpMediaTypeNotAcceptableException.

2. Why is the Vary: Accept header mandatory when implementing content negotiation behind reverse proxies or CDNs?

Answer:
Without Vary: Accept, caching intermediaries (Cloudflare, Akamai, NGINX) store responses using only the HTTP method and URI as the cache key (GET /api/v1/users/42). If Client A requests JSON, the proxy caches the JSON body. When Client B subsequently requests XML at the exact same URI, the proxy serves the cached JSON body to Client B, causing client-side deserialization failures. Vary: Accept forces caches to include the Accept header in their composite cache key.

3. How does a server evaluate Accept: text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8?

Answer:

  1. text/html and application/xhtml+xml have an implicit maximum weight of q=1.0. They represent the client’s highest preference.
  2. If neither is available, application/xml is evaluated next with priority q=0.9.
  3. If XML is also unavailable, the client provides a graceful fallback wildcard */* with priority q=0.8, indicating it will accept any media type rather than receive a 406 Not Acceptable error.

4. What are the operational differences between Accept, Accept-Encoding, and Accept-Charset?

Answer:

  • Accept: Negotiates data serialization schemas and presentation formats (application/json, application/pdf, text/csv).
  • Accept-Encoding: Negotiates byte-level compression transformations (gzip, br, zstd, deflate).
  • Accept-Charset: Negotiates character set encodings (e.g., iso-8859-1 vs utf-8). It is effectively obsolete in modern web development because UTF-8 is the universal mandate across JSON and modern HTTP.

🎯 Interview Cheat Sheet

30-Second Summary

The Accept request header (RFC 9110) drives Server-Driven Content Negotiation by specifying which MIME types the client can process in the response. It supports quality weights (q-values from 0.0 to 1.0) for graceful degradation.

If the server cannot satisfy any of the requested formats, it returns 406 Not Acceptable. Whenever multiple representations are served from a single URI, the server must return Vary: Accept to prevent cache poisoning on CDNs and reverse proxies.

Key Contrast: Accept vs Content-Type

  • Accept (Request only): Declares what the client wants to receive (Accept: application/json).
  • Content-Type (Request & Response): Declares what format the sender is currently transmitting (Content-Type: application/json).

Red Flags (What to Avoid)

  • ❌ “Accept and Content-Type are interchangeable; Accept is just used in requests.” (Content-Type is frequently used in requests too, e.g., POST/PUT payloads).
  • ❌ “406 Not Acceptable indicates an internal server crash.” (It is a client error: the client asked for an unsupported representation).
  • ❌ “Vary: Accept is a security header for HTTPS.” (It is a caching header preventing CDN cache collisions between different representations).
  • ❌ “Modern REST applications should use file extensions like /users.json for content negotiation.” (Spring Boot 3 deprecates path extension negotiation; standard Accept headers must be used).