What is the difference between the accept() and contentType() RequestPredicates, and which HTTP headers do they match?
answer
- Accept = client wants back (response)
- Content-Type = client sends (request body)
- compatibility not equality (wildcards)
- accept -> negotiate GET; contentType -> guard POST
- fall-through 404, not 406/415
basics
~20 saccept() matches the request's Accept header (the media type the client wants back). contentType() matches the request's Content-Type header (the media type of the body the client is sending). They test different headers and are used for different purposes.
solid answer
~40 s`RequestPredicates.accept(MediaType...)` inspects the incoming **Accept** header — what representation the client is willing to receive — and matches if any requested type is compatible with those you list. `RequestPredicates.contentType(MediaType...)` inspects the **Content-Type** header — the media type of the request **body** the client is sending — and matches if it's compatible with your list. So you use `accept` for content negotiation on GETs (serve JSON vs XML), and `contentType` to guard POST/PUT handlers that parse a specific body format. Both use `MediaType` compatibility (which honors wildcards like `application/*`), not strict equality. A common pairing: `POST("/users").and(contentType(APPLICATION_JSON)).and(accept(APPLICATION_JSON))` — accept a JSON body and promise a JSON response. Mixing them up is a classic bug: guarding a body-consuming endpoint with `accept()` won't validate the incoming payload's format.
code
java · 11 linesimport static org.springframework.web.reactive.function.server.RequestPredicates.*;
import static org.springframework.http.MediaType.*;
RouterFunction<ServerResponse> routes(Handler h) {
return route(POST("/users")
.and(contentType(APPLICATION_JSON)) // body must be JSON
.and(accept(APPLICATION_JSON)), // client wants JSON back
h::create)
.andRoute(GET("/report").and(accept(APPLICATION_PDF)), h::reportPdf)
.andRoute(GET("/report").and(accept(APPLICATION_JSON)), h::reportJson);
}go deeper
Know accept = response format wanted, contentType = request body format.
Give correct usage: accept on GET negotiation, contentType on POST guard.
Explain MediaType compatibility semantics and wildcard matching.
Contrast with annotation produces/consumes and the 404-vs-406/415 status-code implications for API design.
**The two headers.** HTTP separates *what the client sends* from *what the client wants back*: - **Content-Type** describes the media type of the message **body** being sent (e.g. a POST payload). On a request, it's set by the client. - **Accept** is a list of media types the client is **willing to receive** in the response, used for content negotiation. **The predicates.** - `RequestPredicates.contentType(MediaType... mediaTypes)` returns a predicate matching if the request's `Content-Type` header is **compatible** with any supplied type. Use it to ensure a handler only runs for bodies it can parse. - `RequestPredicates.accept(MediaType... mediaTypes)` returns a predicate matching if any type in the request's `Accept` header is compatible with any supplied type. Use it for negotiating the response representation. **Compatibility, not equality.** Both rely on `MediaType.isCompatibleWith(...)`, so wildcards work: a request `Accept: application/*` is compatible with `accept(MediaType.APPLICATION_JSON)`, and `Accept: */*` matches anything. An absent header is treated permissively for `accept` (often `*/*`). This means these predicates are *lenient* by design — don't expect exact-string semantics. **Typical usage patterns.** - Read endpoint with negotiation: `GET("/report").and(accept(APPLICATION_JSON))` and a sibling `GET("/report").and(accept(APPLICATION_PDF))` routing to different handlers. - Write endpoint guarding the payload: `POST("/users").and(contentType(APPLICATION_JSON))`. - Both together on a create endpoint: consume JSON, produce JSON. **Matching behavior and 404 vs 406/415.** In the functional model, a failed predicate simply means "this route didn't match," so the request falls through — you generally get a **404** if nothing matches, not the nuanced **406 Not Acceptable** / **415 Unsupported Media Type** the annotation model produces. If you need those precise status codes you must model the routes and fallbacks yourself. This is an important operational difference from `@RequestMapping(produces=..., consumes=...)`. **Gotchas.** - Swapping the two: guarding a POST body with `accept()` instead of `contentType()` validates the wrong header. - Expecting a 415 from `contentType()` — you'll get a fall-through 404 unless you add an explicit catch-all route. - Forgetting compatibility semantics and assuming strict equality. **When to use.** `accept()` for response-format routing; `contentType()` for request-body gating. They're orthogonal and frequently combined.
- If contentType() doesn't match, does the client get a 415?Not automatically. The route just doesn't match and the request falls through — typically a 404 unless you add an explicit fallback route. This differs from annotations' consumes=, which yields 415.
- Do these predicates use exact media-type equality?No — they use MediaType compatibility, so wildcards like application/* and */* match; an absent Accept is treated as */*.
saying these in an interview costs you the question
- Saying accept() checks the request body's media type
- Saying contentType() drives response negotiation
- Assuming a non-matching contentType() returns 415 rather than falling through to 404