skip to content

What does the HTTP specification say about sending a request body with a GET, and what should you do instead when query criteria are too large for a URL?

level: seniorimportance: nice to knowfreq 35%

answer

  1. RFC 9110: GET content has no defined semantics
  2. Clients SHOULD NOT send it; servers may reject
  3. Caches key on URI + Vary headers, never the body
  4. POST /search, or POST-then-GET a results URI
  5. QUERY = safe + idempotent method that takes a body

basics

~20 s

RFC 9110 says content on a GET has no defined semantics, so servers may ignore or reject it and intermediaries may strip it — GET bodies are unreliable. For large criteria use POST with a body, or a two-step POST-then-GET so results stay cacheable.

solid answer

~60 s

RFC 9110 is explicit: **content received in a GET request has no generally defined semantics**, sending it may cause some implementations to reject the request, and clients **SHOULD NOT** generate content in a GET unless it is known to be supported. It is not forbidden by grammar — it is *undefined*, which in practice is worse. The failure modes are real and inconsistent: some proxies, CDNs and load balancers drop the body while forwarding the request; some servers return 400 or 413; some HTTP client libraries silently refuse to attach a body to GET; and HTTP caches key on the **URI and headers**, never the body, so two different "GET with body" requests collide on one cache entry — a correctness bug, not just an inefficiency. The pragmatic answers: (1) `POST /resources/search` with the criteria in the body, accepting the loss of caching; (2) POST the criteria, get an id, then `GET /searches/{id}/results` so results are cacheable and bookmarkable; (3) compress and encode the criteria into the URI. The standardised safe, cacheable **QUERY** method exists precisely to close this gap.

code

http · 12 lines
http
POST /api/orders/searches HTTP/1.1
Content-Type: application/json

{"status":["PAID","SHIPPED"],"createdAfter":"2026-01-01","regions":["EU","US"]}

HTTP/1.1 201 Created
Location: /api/orders/searches/8ab2

GET /api/orders/searches/8ab2/results?page=2 HTTP/1.1

HTTP/1.1 200 OK
Cache-Control: private, max-age=60

go deeper

for a junior

Know that a GET is not supposed to carry a body and that query parameters belong in the URL; use POST when the input does not fit.

for a middle

Cite the undefined-semantics position, note that clients and proxies may drop or reject the body, and give POST /search as the standard workaround.

for a senior

Lead with the caching mechanism — keys come from URI and headers, never the body — and lay out the tradeoff ladder including POST-then-GET result resources and the QUERY method.

for a principal

Set a platform rule for large query interfaces, weighing CDN and cache strategy, observability of reads versus writes, and when to invest in a standardised safe-with-body method.

## What the spec actually says HTTP's message grammar allows content on any request, so a GET with a body is syntactically well-formed. But RFC 9110 states that **content received in a GET request has no generally defined semantics**, that it cannot alter the meaning or target of the request, and that sending it may cause some existing implementations to reject the request. Clients **SHOULD NOT** generate content in a GET unless they know the server supports it. So it is neither forbidden nor meaningful. "Undefined" is the worst category to build on: nothing is required to work, and nothing is required to fail loudly either. ## Why it breaks in practice 1. **Intermediaries.** Reverse proxies, CDNs, WAFs and load balancers vary: some forward the body, some strip it, some reject the request outright. Code that works against your local server can fail after deployment behind a CDN, or fail only for requests that happen to route through one edge. 2. **Servers and frameworks.** Some parse it happily, some ignore it, some return 400. Behaviour has also changed across versions of popular servers. 3. **Clients.** Several HTTP client libraries refuse to attach a body to GET or need special flags; a few silently convert the request to POST. Command-line and browser tooling is similarly uneven — `fetch()` in browsers throws if you give a GET a body. 4. **Caching is body-blind — the fatal one.** An HTTP cache's key is the method plus the effective request URI, refined by `Vary` on *headers*. There is no mechanism to include the body. Two GETs with identical URLs and different bodies are the *same* cache entry, so a shared cache can serve the results of one query to another user's query. That is a correctness and potentially a data-leak bug. 5. **Retries and redirects.** Automatic retries and redirect handling may not replay the body, producing requests with different meaning on retry. ## The legitimate motivation The reason people want it is genuine: a search with many structured filters does not fit comfortably in a query string. URI length has no fixed limit in the specification, but implementations impose their own — browsers, proxies and servers commonly cap somewhere in the low thousands of characters, and servers enforce configurable request-line limits. Deeply nested criteria also encode badly into flat key/value pairs, forcing invented syntaxes. ## The four practical options **1. POST with a body.** `POST /orders/search` with a JSON criteria document. Simple and universally supported. Costs: the request is no longer safe or idempotent by declaration (even though it only reads), responses are effectively uncacheable, and semantically you have labelled a read as a processing action. Most teams take this deal. **2. POST-then-GET (a search resource).** POST the criteria, get back **201** (or 200) with the identifier or `Location` of a stored query, then `GET /searches/{id}/results?page=2`. Now the results have a URI: cacheable, bookmarkable, shareable, paginable. Costs: an extra round trip and server-side state with a lifetime policy. This is the pattern worth naming for large, reusable or expensive queries. **3. Keep it in the URI.** Shorten parameter names, use a compact encoding, or compress and base64url the criteria into one parameter. Retains full cacheability but hurts readability and still has a ceiling. **4. The QUERY method.** The HTTP community standardised a **QUERY** method precisely for this: a method that is **safe and idempotent like GET but carries a request body**, with responses that can be cached against a key derived from the body. It is the correct long-term answer; adoption across intermediaries and frameworks is the gating factor, so treat it as the direction of travel rather than something to deploy blindly today. ## What to say if a system already does it Some widely used systems do accept GET-with-body (several search engines historically did, on their own controlled clients and network paths). That is fine when you own both ends and every hop in between. The moment a public client, a third-party proxy, or a CDN enters the path, the guarantee disappears. If you must support it, also accept POST for the same operation so clients have a fallback, and never rely on shared caching for those responses. ## The interview-worthy summary Name the spec position (undefined semantics, SHOULD NOT generate), then the *mechanical* reason it cannot be rescued by convention — caches key on URI and headers, not the body — and finish with the tradeoff ladder: POST for simplicity, POST-then-GET when cacheable results matter, QUERY as the standardised fix.

  • Why can't caching be made to work for GET-with-body by adding a Vary header?
    Vary refines a cache key using request header fields only; there is no mechanism in HTTP caching to incorporate request content. So two GETs with the same URI and different bodies map to one stored response, and a shared cache may serve one client's results to another. That is why the QUERY method had to define its own body-derived cache key rather than reusing GET.
  • What is the downside of simply using POST /search for every query?
    You lose caching and bookmarkability, and you mislabel a read as a processing action, so intermediaries and clients cannot tell it is safe to retry or prefetch. Instrumentation also degrades: dashboards keyed on method and path can no longer distinguish reads from writes. For expensive or repeated queries, POST-then-GET recovers cacheable, shareable result URIs at the cost of one extra round trip.

saying these in an interview costs you the question

  • Claiming the HTTP specification forbids a body on GET — it says the semantics are undefined
  • Assuming intermediaries preserve GET bodies end to end
  • Expecting caches to distinguish two GET requests by their bodies
  • Believing a Vary header can make GET bodies cacheable
  • Treating GET-with-body as fine because one popular product does it, ignoring proxies in the path

context