#01The Problem Nobody Quite Named Until Now
Every web developer has built the same shape of request more times than they could count: a handful of query parameters bolted onto a URL. It works. It's cacheable, bookmarkable, and every HTTP client, proxy, and CDNContent Delivery Network — a geographically distributed set of edge servers that cache content close to visitors, shrinking the network distance a request has to travel. on the planet already knows exactly what to do with it. For years, this has been the default answer to "how do I ask the server for something without changing anything." GET is safe — it doesn't modify server state — and it's idempotent — running it ten times produces the same result as running it once. Those two properties are what let a browser prefetch a link, a CDN cache a response, and a broken connection retry automatically without anyone worrying about accidentally submitting a form twice.
The trouble starts the moment the query stops being a handful of flat key-value pairs. A real search feature wants nested filters, multiple sort keys, date ranges, facets, and sometimes a raw query language of its own — the kind of structure a ?query string was never built to hold. RFC 10008, the specification at the center of this piece, is explicit about why that matters: cramming a complex query into a URI runs into real limits on URI length, the escaping and re-escaping overhead of encoding structured data as flat text, and the fact that anything in a URL tends to end up somewhere it probably shouldn't — access logs, browser history, referrer headers, and anyone glancing at a bookmarks bar. A search for "salary negotiation tips" or a filter containing a customer's account number doesn't stop being sensitive just because it's technically "just a GET."
Long before any of this had a name, developers ran into the same wall and improvised around it. The common workaround — still visible in plenty of production APIs today — is folding the whole structured query into a single opaque parameter: JSON-stringify the filter object, then base64- or URL-encode the result into something like ?q=eyJmaWx0ZXJzIjpb.... It technically keeps the request on GET, which preserves cacheability and safety, but it trades away everything that made query strings useful in the first place — the parameter is no longer human-readable in a log line, no longer editable by hand, and no longer meaningfully different from a POST body except that it's now also subject to URL length limits and encoding overhead on top. It's a workaround that papers over the actual gap rather than closing it, which is exactly the kind of accumulated, slightly-wrong-for-everyone convention that tends to show up once enough developers hit the same unsolved problem independently.
Those URI length limits aren't a vague concern — they're specific, and they differ depending on which piece of infrastructure a request happens to pass through. Apache's LimitRequestLine defaults to roughly 8,190 bytes; nginx's large_client_header_buffers defaults to 8 KB; RFC 9110 itself recommends that servers support URIs of at least 8,000 octets, without guaranteeing anything beyond that. Cloud load balancers add their own, often non-configurable ceilings on top. A query that's simple today can drift past any of those limits the moment a product adds one more filter type, one more sort field, or one more facet — and the failure mode isn't a clean error, it's a 414 URI Too Long from whichever layer hit its limit first, often nowhere near the application code that actually needed the extra parameter.
The problem was never that GET is wrong for simple lookups — it's that "safe and idempotent" and "has a real place to put a complex payload" have never, until now, been true of the same HTTP method at the same time.
What Actually Happens When You Type a URL covers how a browser actually builds and sends that GET request in the first place — worth a read if the handshake-level mechanics behind these examples aren't already familiar. This piece stays one layer up: the design problem HTTP itself has had since GET and POST were first defined, and the method the IETF just standardized to close it.
Concretely, a request built around that instinct looks like this:
GET /feed?q=database&limit=100&sort=-published&category=tech&category=opinion
#02POST Solves the Body Problem — By Breaking a Different Guarantee
The obvious workaround has been around for as long as REST APIs have: if the query is too complex for a URL, send it as a POST body instead. This genuinely solves the size and structure problem — a JSON body can express anything a ?query string can't, with no encoding gymnastics required. But it trades one problem for a more subtle one: POST carries no safety or idempotency guarantee at all. Per RFC 9110, the HTTP specification POST is defined against, a POST request's whole reason for existing is to let the target resource do something — process a payload, append to a resource, trigger a side effect. A client, a proxy, or a Load BalancerA component that distributes incoming traffic across multiple backend server instances, so no single instance becomes a bottleneck or single point of failure. has no way to know, just from the method name, whether replaying that exact request is harmless or whether it just placed a second order.
Some teams work around this today with an "Idempotency-Key" request header — the client generates a unique Idempotency KeyA unique token attached to a request so that retrying the same request never causes it to be processed twice — e.g. preventing a duplicate payment charge.Learn more per logical operation, and the server deduplicates based on that key rather than trusting the method itself. It's a real, widely used pattern, and it works. But it's also application-level machinery bolted on to compensate for something the protocol doesn't guarantee — every service has to implement its own key-generation, storage, and deduplication logic, and every client has to know to send one, because HTTP's own method semantics offer no help here. It solves the same problem QUERY solves at the protocol level, just per-endpoint, by hand, at the cost of code every team has to write and maintain themselves.
That ambiguity is why "search" endpoints implemented as POST quietly lose things a GET-based search gets for free. An HTTP client's automatic retry-on-timeout logic won't safely retry a POST the way it retries a GET, because retrying might mean re-submitting a side effect, not just re-asking a question. A cache can't treat POST responses as reusable by default, since nothing tells it the request was read-only. A browser won't prefetch it, back-button navigation treats it specially, and infrastructure built around "safe methods can be repeated, unsafe ones can't" simply doesn't apply. None of that is a POST bug — POST is doing exactly what it was specified to do. The problem is that "query with a body" and "action with a body" have been forced to share one method for the entire history of HTTP, and infrastructure has had to guess which one it's actually looking at.
A method's name is the only signal most of the internet's plumbing ever gets — POST never told anyone whether it was safe to retry, because it was never supposed to be.
Concretely, that same feed search from the previous section, rebuilt on POST:
POST /feedContent-Type: application/json{"query": "database","filters": { "category": ["tech", "opinion"] },"sort": [{ "field": "published", "direction": "desc" }],"limit": 100}
#03Introducing QUERY: The Missing Middle Ground
In June 2026, the IETF published RFC 10008: The HTTP QUERY Method as a Proposed Standard on the Standards Track — not a draft proposal, not an experimental extension, but a real addition to the HTTP method registry that any server or client is now free to implement against a stable specification. It was authored by Julian Reschke (greenbytes), James M. Snell (Cloudflare), and Mike Bishop (Akamai) — all names with a long history in HTTP standardization. Snell, notably, is also a co-author of RFC 5789, the 2010 specification that gave HTTP the PATCH method. QUERY is, by that measure, the first genuinely new general-purpose HTTP method in sixteen years.
The method's own definition is short and direct: a QUERY request asks the target resource to process the content enclosed in the request body, in a manner that is both safe and idempotent, and to respond with the result of that processing. In practice, that means QUERY takes the one thing POST had that GET didn't — a request body — and pairs it with the one thing GET had that POST didn't: a guarantee that the request can be retried, cached, and prefetched without any risk of a side effect.
This wasn't a fast-tracked idea. The IETF HTTP Working Group first adopted the underlying draft in 2021, framed at the time as a generalized version of WebDAV's SEARCH method; the method itself was renamed from SEARCH to QUERY by the draft's second revision in 2022, once the working group settled on a name that described a general capability rather than one specific use case. Fourteen draft revisions and roughly five years later, that document became RFC 10008. For a piece of internet infrastructure as foundational and conservatively maintained as HTTP's method vocabulary, that pace isn't unusually slow — it's what "getting it right instead of getting it fast" actually looks like in the IETF's own process.
The point isn't that QUERY replaces either existing method — it's that it fills a slot HTTP has simply never had, summarized against the two methods it sits between, and in the specification's own words below.
QUERY isn't a third option alongside GET and POST — it's the specific combination neither of them was ever allowed to offer at the same time.
| Method | Body | Safe | Idempotent | Typical purpose |
|---|---|---|---|---|
| GET | Optional, limited semantics | Yes | Yes | Retrieve a resource |
| POST | Yes | No | No | Submit or process data, trigger a side effect |
| QUERY | Yes | Yes | Yes | Perform a read-only query |
#04What "Safe" and "Idempotent" Actually Mean Here
It's worth being precise about these two words, since they're doing all the real work in this specification and it's easy to skim past them as boilerplate.
Safe, in RFC 9110's own terms, means the client does not request, and does not expect, any state change on the target resource as a result of the request. A safe method can still have side effects a server chooses to add — logging, analytics counters, incrementing a "view count" — but those are the server's own bookkeeping, not something the client asked for or should rely on. GET, HEAD, OPTIONS, and TRACE were already safe. QUERY joins that list.
Idempotent means that making the same request N times has the same effect on server state as making it once. It says nothing about the response being identical every time — a query against a live dataset can legitimately return different rows on Tuesday than it did on Monday — it's a guarantee about what the request does to the server, not about the stability of the answer. GET, HEAD, PUT, and DELETE were already idempotent (DELETE stays idempotent because deleting an already-deleted resource is still "deleted" — the state doesn't change on the second call).
Safe and idempotent aren't a courtesy label attached after the fact — they're what let a client, a proxy, or a piece of infrastructure treat a request as retryable without having read a single line of the application code behind it.
This is precisely why the combination matters for anyone building resilient systems. A retry policy, a circuit breaker, an at-least-once delivery queue, or a client library's automatic-retry-on-5xx logic can all safely apply to QUERY the same blanket rule they already apply to GET — because the specification, not a case-by-case judgment call, guarantees it's safe to do so.
Consider what happens today when a POST-based search endpoint times out mid-request. A well-behaved HTTP client generally won't retry it automatically, because the client library has no way to know whether the timeout happened before or after the server started processing a side effect — and a search endpoint genuinely built to only read data still gets treated with the same caution as one that charges a credit card, purely because POST doesn't distinguish between the two. Multiply that across a Service MeshAn infrastructure layer (Istio, Linkerd, Cilium) that handles service-to-service communication in a microservices architecture — mTLS, traffic routing, rate limiting, and observability, via sidecar proxies. with dozens of internal search-style calls, and the practical cost is real: engineers either accept that transient failures surface as user-facing errors more often than they need to, or they hand-roll retry logic specific to each endpoint they've personally verified is safe — which is exactly the kind of tribal knowledge that HTTP's method semantics exist to make unnecessary. A QUERY endpoint doesn't need that manual verification. Its safety is declared at the protocol level, which means every piece of off-the-shelf retry and resilience tooling already knows how to treat it correctly, with zero endpoint-specific configuration.
#05Reading the Spec: How QUERY Actually Works on the Wire
The mechanics of a QUERY request are closer to POST's shape than GET's, with a few deliberate constraints layered on top specifically because the method has to stay safe and idempotent.
Every one of those constraints exists to make one promise enforceable at the protocol level: a server can't accidentally treat an ambiguous request as safe, and a client can't accidentally treat an unsafe one as retryable.
A Content-Type header is mandatory — the server needs to know what grammar the body is written in (JSON, a form-encoded query string, SQL, GraphQL, a custom query DSL) before it can process it, and the specification requires the server to reject a request that's missing one or whose declared type doesn't match the actual content. Concretely:
QUERY /contacts HTTP/1.1Host: api.example.comContent-Type: application/x-www-form-urlencodedAccept: application/jsonname=Smith&department=sales
#06Status Codes, Caching, and How Clients Discover QUERY Support
The response side reuses HTTP's existing status code vocabulary rather than inventing new ones — a missing or mismatched Content-Type is a 400, an unsupported one is a 415, a well-formed but semantically invalid query is a 422, and a request the server can't produce a representation for (per the Accept header) is a 406, with a successful query returning the usual 200 and results in the body. Reusing the existing vocabulary rather than inventing QUERY-specific codes is deliberate — it's what keeps QUERY interoperable with every existing HTTP client's error handling, summarized in the table at the end of this section.
Two response headers carry weight specific to QUERY. Content-Location can point at a GET-able resource representing this particular result — useful when a result set is worth exposing as its own addressable thing, even temporarily. Location can point at an "equivalent resource" derived from the target plus the request content, meaning a client can turn a QUERY into a plain GET against that URI for any later repeat of the same query, picking up ordinary HTTP caching and conditional-request support along the way for free.
That mechanism is worth walking through concretely, since it's the piece of the specification that turns QUERY from "a body-carrying GET" into something that plays well with the rest of the web's caching infrastructure. A client sends the contacts search from earlier — QUERY /contacts with name=Smith&department=sales in the body. The server processes it and, alongside the 200 response and its results, includes a header like Location: /contacts?q=eyJuYW1lIjoiU21pdGgifQ. That URI isn't meant to be human-readable — it's an opaque, server-generated identifier for this exact query — but it is a real, GET-able resource from this point forward. The next time the client (or any client) needs the identical result, a plain GET against that URI works, benefits from ordinary HTTP caching without any body-aware cache key required, and supports If-None-Match/If-Modified-Since conditional requests the same as any other resource. The QUERY did the expensive, structured work once; the URI it handed back lets everyone after that first request treat the result like any other cacheable GET.
Redirects behave the way they would for any safe method: a 301 or 308 suggests retrying the same QUERY against a new URI, a 302 or 307 signals a temporary relocation, and a 303 can redirect the client to a GET.
Caching is the part of the specification that asks the most of implementers. Because two QUERY requests to the identical URI can carry completely different bodies and mean completely different things, a cache can't key on the URL alone the way it does for GET — the Cache Key has to incorporate the full request content and its metadata, including the media type. The specification does allow some normalization for cache efficiency (stripping content-encodings, normalizing per media-subtype conventions like +json), and a client can force exact-content matching with the no-transform cache directive if that normalization would ever produce a false-positive cache hit. Conditional requests (If-None-Match, If-Modified-Since) and range requests both work against QUERY the same way they do against GET, applied to whatever "equivalent resource" the response identifies.
Servers advertise QUERY support up front through a new Accept-Query response header, listing which media types a given path accepts as query grammars. A response carrying Accept-Query: application/json, application/x-www-form-urlencoded, application/jsonpath tells a client, before it ever sends a QUERY request, that this endpoint understands three different query grammars and lets it pick whichever one it already speaks. That header applies to every URI sharing the same path (ignoring the query component), so a client integrating against an unfamiliar API can discover whether QUERY is even supported there — and in what formats — with a single OPTIONS or GET request, rather than guessing and handling a 415 as the discovery mechanism.
None of this is inventing new HTTP machinery — status codes, conditional requests, and redirects all work exactly as they already do, which is precisely what makes QUERY something existing infrastructure can grow into rather than something it has to be rebuilt around.
As a quick reference, here's how a QUERY request's outcome maps onto status codes a client already knows how to handle:
| Situation | Status code |
|---|---|
| Missing Content-Type | 400 Bad Request |
| Content-Type doesn't match the actual body | 400 Bad Request |
| Unsupported Content-Type | 415 Unsupported Media Type |
| Well-formed but semantically invalid query | 422 Unprocessable Content |
| No representation matches the Accept header | 406 Not Acceptable |
| Successful query | 200 OK, with results in the response body |
#07Does QUERY Mean We Should Start Replacing POST and GET?
No.
This is the part of the specification's story that's easiest to overstate, and probably the single most important thing to get right in how it gets talked about. QUERY is a specialized tool for a specific shape of problem, not a general replacement for either of the methods it sits between — and treating "we have a new HTTP method now" as a reason to go re-architect every existing endpoint is exactly the kind of overcorrection that tends to follow any genuinely useful new tool into the wild.
The cases where QUERY is genuinely the right call share three properties: the operation is fundamentally a query — it reads, it doesn't write; the request needs a body because the query is too large or too structured for a URL; and the safety/idempotency guarantee is actually valuable to the client or the infrastructure sitting in front of it — worth having retries, caching, or prefetching behave correctly.
Plenty of real endpoints don't meet all three. A login endpoint isn't a query at all, no matter how you squint at it — authenticating a credential is fundamentally an action with consequences (a session gets created, a failed-attempt counter increments), not a read, regardless of how safe-looking the request/response shape might seem on the surface. A simple lookup by ID belongs on GET, full stop — GET /users/482 has no body-size problem to solve, no encoding overhead worth avoiding, and converting it to QUERY /users/482 buys nothing while quietly losing GET's universal browser, CDN, and tooling support for no real benefit. An endpoint that logs every call as a billable, metered "API request" — the kind many SaaS platforms run — arguably wants exactly the accountability semantics QUERY is built to sidestep, since QUERY's whole point is letting infrastructure treat repeats as free to retry. And a mutation disguised as a read — a "track this page view" or "record this search term" endpoint that also happens to return data — is precisely the shape QUERY's safety guarantee explicitly forbids: if a client retrying the request twice would double-count anything server-side beyond the server's own incidental bookkeeping, it was never a safe operation to begin with, however read-like the response looks.
Converting every /users endpoint into a blanket QUERY /users because the method exists now would be exactly the kind of API design QUERY's own authors would recognize as missing the point — trading a method everything already understands for one that solves a problem the endpoint never actually had.
QUERY earning a place in an API isn't about novelty — it's about whether the endpoint's request genuinely needs a body and its response genuinely needs to be safe to retry, cache, and prefetch at the same time.
#08Real-World Examples Worth Actually Looking At
An analytics API is close to the canonical use case the specification has in mind — a request that's unambiguously read-only, with a payload no query string could hold cleanly: a dimension/metric/filter/sort combination that would be unwieldy and fragile as a ?query string. Because the whole thing is safe and idempotent, an analytics dashboard is free to retry it on a flaky connection, or let a CDN cache the exact same query issued by two different users, without anyone auditing the endpoint's internals first to confirm it's actually harmless.
That last point matters more than it might first appear. An analytics dashboard rendering a dozen widgets on page load, each backed by a slightly different dimension/metric combination, is exactly the kind of screen that used to force a choice between two bad options: implement each widget's query as GET and accept the URL-encoding overhead and length risk from earlier in this piece, or implement it as POST and lose the ability to let a CDN or browser cache absorb any of that load, even for the widgets multiple users are requesting identically within the same minute. QUERY removes the trade-off entirely — the dashboard gets POST's expressiveness and GET's cacheability on the same request, without picking one cost to live with.
A product-search endpoint with facets, multi-field sorting, and pagination state sits in the same category — filters as a structured list rather than repeated ?filter= parameters, facet selections that would each need their own escaped query key, a sort array instead of a single ?sort= value, and pagination metadata (page, cursor, pageSize) that a deeply nested URL only makes harder to read, not easier.
The shared thread across both examples isn't "search is special" — it's that the query's shape outgrew what a URL can hold long before its meaning stopped being a plain read.
Concretely, the analytics example from above, on the wire:
QUERY /analytics HTTP/1.1Host: api.example.comContent-Type: application/jsonAccept: application/json{"dimensions": ["country", "device"],"metrics": ["sessions", "revenue"],"filters": { "date": "2026-08-01..2026-08-10" },"sort": "-revenue"}
#09Isn't This Just... GraphQL?
Anyone who's spent time building APIs has almost certainly had the same thought by this point: GraphQL solved "complex query, needs a body" years ago. It's a fair question, and the honest answer is that GraphQL and QUERY are solving overlapping problems at different layers, not competing solutions to the same one.
GraphQL queries have been sent as POST requests since the project's earliest days, for precisely the reason this piece has spent several sections on — a GraphQL query document, with its nested field selections and variables, was never going to fit cleanly in a URL. That decision has had a real, ongoing cost that GraphQL's own community has been open about: CDNs and edge proxies don't cache POST requests, so nearly every GraphQL query in production today skips right past the CDN layer and hits the origin server directly, even for queries that are, semantically, completely safe reads that thousands of different users might be issuing identically. GraphQL solved the "body" half of the problem in 2015 and has been quietly absorbing the caching cost of that decision ever since.
QUERY is exactly the right method for a GraphQL query on paper — safe, idempotent, body-carrying, cacheable — and there's real interest in the GraphQL community in eventually sending queries that way. But as of RFC 10008's publication, that's still just interest, not a shipped feature: the GraphQL-over-HTTP specification hasn't adopted QUERY, and RFC 10008 itself is scrupulously silent on payload formats — it defines a method, not a query language, which is precisely why it can sit underneath GraphQL, SQL, JSONPath, or a bespoke filter DSL with equal indifference to which one a given API chooses.
GraphQL answers "what query language should my API speak"; QUERY answers "what HTTP method should carry it once I've decided" — they're not actually competing for the same decision.
The more useful way to think about the relationship: QUERY is a plausible future transport for GraphQL, not an alternative to it, and adopting QUERY doesn't require adopting GraphQL any more than adopting POST ever did.
#010QUERY Has Ancestors — It Didn't Appear From Nowhere
RFC 10008 is explicit that it isn't the first attempt at "safe method with a body." WebDAV, the HTTP extension suite built for remote authoring and versioning, already shipped several methods with exactly that shape: PROPFIND for retrieving properties, SEARCH (RFC 5323) for searching a resource collection, and REPORT for structured queries in versioning contexts. Each of those is safe, each accepts a request body, and each has been running in production WebDAV servers for close to two decades.
PROPFIND is the most widely deployed of the three in practice — every WebDAV client, from macOS Finder's network-drive support to Nextcloud's sync engine, sends a PROPFIND with an XML body describing which properties it wants back, and gets a structured multi-status response in return, all safely and repeatably. SEARCH generalized that idea to full resource-collection queries, letting a client describe filter criteria in a request body against a WebDAV-enabled collection rather than a single resource. REPORT, defined for the WebDAV Versioning extensions, exists specifically for the kind of structured, potentially complex queries version-control-style systems need — "give me every revision of this resource between these two dates" being exactly the shape of question a URL was never well suited to ask.
What QUERY adds isn't the underlying idea — it's generality. PROPFIND, SEARCH, and REPORT are all scoped to WebDAV's own domain and its own body formats (mostly XML, mostly describing WebDAV-specific concepts like properties and locks); QUERY is deliberately format-agnostic, discoverable through Accept-Query rather than assumed, and defined as a first-class HTTP method any API can adopt without pulling in WebDAV's much larger surface area — locking, versioning, collections — to get there. That's also where the method's name comes from — not "search," which the specification's authors considered and rejected as too narrow (a name that implies text search specifically, when the method needs to support arbitrary structured queries), but "query," chosen specifically to map onto the URI's own query component and to describe a general read-only operation rather than one particular use case.
The sixteen-year gap since PATCH is worth sitting with for a moment. HTTP's method vocabulary is deliberately conservative — every new verb is something every future client, proxy, cache, and security tool has to learn about, forever, which is exactly why the IETF doesn't add one lightly. QUERY clearing that bar is itself a signal that the gap it fills was a real, widely felt one, not a convenience nice-to-have.
HTTP has added a new method roughly once a decade, at most — that scarcity is the strongest evidence that QUERY is solving a problem the ecosystem has actually been stuck on, not one it just noticed.
#011The Adoption Reality: A Spec Ships Faster Than an Ecosystem
Publishing an RFC is the easy part, comparatively — the entire rest of the web's infrastructure now has to actually learn a new word.
A specification only has to be written once; every client library, framework router, proxy, and cache that touches HTTP has to individually decide to support what it now allows.
Server frameworks are furthest along, largely because supporting a new HTTP method on the server side is a comparatively contained change. .NET 10, released as an LTS in November 2025 — before the RFC even formally published — shipped QUERY support on both ends of the wire, treating HttpMethod.Query as idempotent out of the box so it plugs directly into Microsoft.Extensions.Http.Resilience's automatic-retry policies the same way GET already does, no special-casing required. On the client:
using var request = new HttpRequestMessage(HttpMethod.Query, "https://api.example.com/products/search"){Content = JsonContent.Create(filter)};using var response = await httpClient.SendAsync(request);
#012Where the Rest of the Ecosystem Actually Stands
Server-side, ASP.NET Core exposes HttpMethods.Query and HttpMethods.IsQuery(...), and Kestrel parses the method natively; routing it takes the generic MapMethods helper, since dedicated convenience wrappers like MapQuery(...) were deliberately left out of the initial release. Elsewhere, the picture is a genuine mix of shipped, in-progress, and stalled — summarized in the table at the end of this section.
Go's situation is a useful reminder that "no official support yet" and "doesn't work" aren't the same thing. Go's standard library has never restricted net/http to a fixed list of method names — since Go 1.22 added pattern-based routing, a handler can be registered against any method string, QUERY included, and a client can issue one with nothing more than http.NewRequest("QUERY", url, body). The open proposal for a dedicated http.MethodQuery constant is about ergonomics and self-documentation, not capability — the method already works, a named constant just means developers stop typing the literal string by hand. Rails sits at the opposite end of that spectrum: Action Pack's router actively rejects QUERY before a controller action ever sees it, unless a project patches in explicit support itself, which means "custom methods just work" isn't a safe assumption to carry from one framework to the next — it depends entirely on whether that framework's router was built to allow arbitrary methods through or to validate against an internal allowlist.
curl's support is a useful case study in how little friction a client-side implementation actually needs — Daniel Stenberg, curl's longtime maintainer, confirmed sending QUERY works exactly like any other custom method (curl -X QUERY -d '{"query":"..."}' https://example.com/), with one real gotcha: curl's legacy --location/-L redirect-following flag rewrites the method on every redirect regardless of what the server's response code actually calls for, which silently violates QUERY's redirect semantics from earlier in this piece. The newer --follow flag respects the distinction between a 301/308 (repeat as QUERY) and a 303 (switch to GET) instead.
Spring's case is a small, telling example of how much friction even a server-side addition can hit in a mature framework: beyond the missing RequestMethod.QUERY enum value itself, the obvious annotation name — @QueryMapping — is already taken by Spring GraphQL, so even a future implementation can't reuse the naming convention @GetMapping/@PostMapping established without colliding with an existing, unrelated feature.
A new HTTP method doesn't just need a parser to recognize it — it needs a name in every framework's routing table, and sixteen years of nobody needing that name means it's often already taken by something else.
Browsers are the real bottleneck. As of RFC 10008's publication, no browser can send a QUERY request through fetch() or XMLHttpRequest at all. An open WHATWG Fetch issue lays out exactly why this isn't a quick fix: the Fetch spec only uppercase-normalizes six specific methods today, and QUERY isn't one of them, so fetch(url, { method: 'query' }) would currently send a lowercase, non-standard method on the wire rather than QUERY. QUERY also isn't CORS-safelisted, which the RFC intends deliberately, but which means every cross-origin QUERY triggers a preflight OPTIONS request — exactly like a POST with a JSON body does today, just one more thing browser implementers have to wire up correctly. And Fetch's cache implementation keys purely on URL; RFC 10008's own caching model requires the request body as part of the cache key, which Chrome and Firefox have both confirmed, empirically, they don't currently do for repeated identical QUERY calls. Mozilla's standards-positions repository has an open request for an official position on QUERY that, as of this writing, remains unscreened — no browser vendor has taken a public stance yet.
As a snapshot, here's where the pieces most likely to matter for a real integration stand as of mid-2026:
| Component | Status |
|---|---|
| .NET 10 (client + server) | Shipped |
| Node.js core HTTP parser | Recognizes QUERY natively |
| Node.js undici (fetch client) | Tracked, not yet shipped (nodejs/undici#5454) |
| Eclipse Jetty / Apache Tomcat | Server-side support in place |
| Spring Framework | Request closed as a duplicate; no RequestMethod.QUERY yet |
| OpenAPI 3.2 | Native query operation support (shipped September 2025) |
| curl | Works today via -X QUERY / --request QUERY |
| nginx | QUERY method support added |
| Go net/http | No dedicated constant yet (proposal open, golang/go#80058) — usable today regardless, since Go's router and client have treated the method name as an arbitrary string since Go 1.22 |
| Ruby on Rails | Action Pack rejects QUERY at the routing layer until a project adds explicit support — proposed on the Rails discussion forum, not yet merged |
| Browsers (fetch/XHR) | Cannot send QUERY yet — method not normalized, no browser has shipped it |
#013When Infrastructure Doesn't Recognize a New Verb
The gap between "the method is specified" and "the internet's plumbing knows what to do with it" isn't hypothetical — it shows up the moment someone actually deploys QUERY to a real edge network, and one widely shared account from mid-2026 makes the shape of the problem concrete.
A developer testing a QUERY endpoint behind Vercel's edge network ran a simple burst test — twenty identical requests each for GET, POST, and QUERY against the same handler. GET and POST came back clean, 200 across the board. QUERY didn't — not because of anything wrong with the request itself.
The application code behind the endpoint — running on Python, Node, and Supabase's Deno runtime in the same test — handled QUERY correctly every time. The requests never got that far. Vercel's edge-level bot mitigation intercepted them first, returning an X-Vercel-Mitigated: challenge response starting at roughly the fourth request in the burst. The heuristic wasn't malfunctioning, exactly — it was doing precisely what it was built to do: flag an unfamiliar HTTP verb arriving in a repeated, automated-looking pattern as likely scanner or bot traffic, because as of mid-2026, essentially no real browser traffic sends QUERY, so a burst of it genuinely does look anomalous by any behavioral standard trained on today's traffic. The workaround exists — bot management can be disabled or bypassed per-deployment with a token — but it has to be applied deliberately, endpoint by endpoint, until edge security tooling across the industry updates its own baselines for what "normal" HTTP traffic looks like.
What makes this particular story worth including rather than just noting in passing is how little it had to do with QUERY's actual design. Nothing about the request was malformed, nothing about the payload was dangerous, and nothing in RFC 10008 was violated — the entire failure happened one layer below the application, in a heuristic that had never been trained on this verb existing at all. That's a fundamentally different category of problem than a bug, and it's one no amount of careful RFC-writing could have prevented, because it isn't a specification problem. It's the same lag every previous new HTTP method has had to live through — PATCH spent its own early years occasionally rejected by intermediaries that only knew GET, POST, PUT, and DELETE — and it resolves the same way each time: slowly, deployment by deployment, as more real traffic normalizes what "expected" looks like.
This is the real shape of "the RFC shipped" versus "the method works everywhere": the specification is finished, but every CDN, WAF, and bot-detection heuristic trained on twenty-five years of GET/POST-only traffic has to individually relearn what normal looks like.
That's not a flaw in QUERY, and it isn't really a flaw in Vercel's bot mitigation either — it's the ordinary, unglamorous lag between a standard being published and the accumulated pile of infrastructure built on assumptions the standard just changed. Anyone adopting QUERY in production today should expect to hit some version of this and budget time for it, not treat it as a sign the method itself is broken.
The pattern, side by side, was stark:
GET x20 -> 200,200,200,200,200,200,200,200,200,200,...POST x20 -> 200,200,200,200,200,200,200,200,200,200,...QUERY x20 -> 200,200,200,403,403,403,403,403,403,403,...
#014Getting QUERY Into Production Today, Without Waiting for Browsers
None of the gaps in the previous section mean QUERY is unusable today — they mean adopting it early takes a slightly different shape than flipping a switch. Three things make that shape manageable.
Start server-side and stay there until clients catch up. A server can add a QUERY handler next to an existing POST-based search endpoint without removing anything — clients that already speak QUERY (a growing list of backend-to-backend integrations, CLI tools built on curl, .NET services calling other services) can use it immediately, while browser-based frontends keep using POST until fetch() actually supports the method. This is exactly the shape .NET 10's own migration guidance recommends, and it costs almost nothing: the same handler logic can usually serve both, since both arrive with a JSON body and the same validation applies either way — Node's raw http module and most frameworks built on it already pass an unrecognized method straight through, so receiving one takes nothing more than a check for it, no QUERY-specific parsing required.
Advertise support explicitly, rather than assuming a client will guess. Setting the Accept-Query response header on an endpoint costs one line of server configuration and lets any client — human or automated — discover that QUERY works there before attempting it blind. It's a small thing, but it's also the one piece of self-description RFC 10008 actually gives implementers, and skipping it means every client integrating against the endpoint has to be told out-of-band instead.
Test through the exact infrastructure the request will actually pass through, not just the application. The Vercel story earlier in this piece is the sharpest possible illustration of why "the application code handles QUERY correctly" and "QUERY works in production" are two different claims. Before relying on a QUERY endpoint anywhere that matters, send real traffic through the actual CDN, WAF, API Gateway, and load balancer sitting in front of it — not just against the application directly — and specifically watch for anything that behaves differently at method level than it does for GET or POST. A handful of identical requests sent as GET, POST, and QUERY, compared side by side, is enough to surface most of these issues before a real user does.
Adopting a brand-new HTTP method isn't really about the method — it's about finding every piece of infrastructure between a client and a server that's never seen that method's name before, one layer at a time.
In practice, that dual-method handler is often this simple:
app.use('/products/search', (req, res, next) => {if (req.method === 'QUERY' || req.method === 'POST') {return handleSearch(req, res);}next();});
#015What About Security?
QUERY's security posture is mostly a net improvement over the GET-with-a-huge-query-string pattern it's meant to replace, with a couple of new considerations worth knowing rather than fearing.
Moving query content out of the URL and into the request body is, on its own, a genuine privacy and security win: URLs get logged by web servers, proxies, browser history, and analytics tools by default, in a way request bodies generally aren't — so a search containing a customer's name or account details leaks into far fewer places as a QUERY than it would as the equivalent ?q= parameter.
The specification does add its own new surface area to think about, though, and a handful of security researchers have already started cataloguing where the ecosystem's tooling, not the RFC itself, is the actual weak point during this transition. None of the gaps below are flaws in QUERY's design — they're the predictable cost of introducing a request shape most security tooling has never had to reason about before.
The broader, less code-level point is the one the Vercel anecdote already illustrates: web application firewalls, bot-detection systems, and security scanning tools built their heuristics around a world where "safe, read-only, repeatable request" and "GET" were synonymous. QUERY breaks that assumption on purpose, and security tooling across the industry — not just Vercel's — will need time to learn to treat a QUERY body the way it already treats a POST body: something to inspect and validate, not something to be suspicious of purely because the verb is unfamiliar. The practical response, in each case below, is straightforward: test identical payloads through both POST and QUERY against any WAF or gateway in front of a new endpoint, confirm cache-key generation actually hashes the full body rather than the URL alone, and audit CSRF configuration for hardcoded method lists rather than assuming a "safe method" label means nothing needs checking.
QUERY doesn't make request bodies more dangerous than they already were on POST — it just means "safe method" can no longer be used as a shortcut for "body-free method" when a security tool decides what to trust.
- WAF and inspection gaps. A web application firewall configured to inspect POST and PUT bodies for injection attempts has no reason to also inspect a QUERY body unless someone has explicitly told it to — and some frameworks currently reject QUERY outright before it ever reaches application code, which sounds safe but actually means the request never gets the framework's own validation either. Rails' Action Pack, for instance, rejects QUERY at the routing layer entirely unless a project adds explicit support for it — not a vulnerability by itself, but exactly the kind of "silently different code path" that's easy to get wrong once support is bolted on later.
- Cache poisoning. RFC 10008 requires the cache key to incorporate the full request body, not just the URL — which means an implementation that gets that normalization wrong (hashing only part of the body, or normalizing two meaningfully different queries into the same key) can end up serving one user's query results to a completely different user who happened to send a textually similar QUERY.
- CSRF assumptions built for a smaller method list. CSRF protections have historically been scoped to the methods that can carry a body and change state — POST, PUT, DELETE, PATCH. QUERY carries a body too, and while the specification defines it as safe, an endpoint with server-side logging or rate-limit bookkeeping attached to it (the kind of side effect the safety guarantee explicitly still allows) can end up outside a CSRFCross-Site Request Forgery — an attack that tricks a logged-in user's browser into making an unwanted request to a site it's authenticated with, exploiting cookies the browser sends automatically. middleware's allowlist simply because nobody added it yet.
- Request smuggling between mismatched parsers. Any time a front-end proxy and a back-end server can disagree about how to parse the same request, there's room for a desync attack — and a new method name is exactly the kind of thing older, unpatched components might handle inconsistently from newer ones sitting in front of or behind them.
#016The RFC Itself Isn't Perfect — And That's Fine
Two errata have been reported against RFC 10008 since its June 2026 publication, both currently sitting at "reported" status rather than verified or accepted by the IETF's errata process. Neither touches the method's actual design — both are exactly what they look like below: proofreading slips in illustrative appendix examples, not flaws in QUERY's safety, idempotency, or caching semantics.
A typo in a worked example and a flaw in a method's semantics are entirely different categories of problem — RFC 10008 has reports of the first kind, not the second.
It's worth naming them precisely because pretending a brand-new RFC arrived flawless would be its own kind of overhype — but two clerical erratum reports in an appendix, months after publication, is a genuinely unremarkable state for a freshly published specification to be in, not evidence anything about the method itself needs rethinking.
| Erratum | Section | What it flags |
|---|---|---|
| 9013 | A.5 | A worked example shows a Vary header on a request message — Vary is only ever valid on a response per RFC 9110, almost certainly a copy-paste artifact from the 304 response example shown just below it |
| 9016 | A.4.2 | An example's Last-Modified date spells out "November" instead of the three-letter "Nov" the IMF-fixdate format requires, plus a couple of stray commas after years |
#017QUERY Is Not About Adding Another Verb. It's About Giving Queries a Proper Place in HTTP.
Every piece of this — the RFC's own language, the comparison against GET and POST, WebDAV's decades-old precedent, the still-patchy adoption picture, even a CDN's bot-mitigation system briefly mistaking it for an attack — points at the same underlying fact. HTTP has always had a shape for "give me something back without changing anything" and a shape for "here's a complex payload to act on," but never, until this year, a single shape that was honestly both at once.
It's worth being honest, too, about what this piece has spent a lot of words establishing won't happen quickly. QUERY doesn't have browser support yet, and won't for some unknown stretch of time — the Fetch specification issue tracking it is still open, still labeled as needing implementer interest, and no browser vendor has committed to a timeline. CDNs, WAFs, and bot-detection systems will keep occasionally treating it as anomalous traffic until their training data catches up with a method that's actually been standardized. Frameworks will keep shipping partial support, hitting naming collisions with existing features, and closing feature requests as duplicates while the underlying work continues elsewhere. None of that is a reason to wait entirely — server-to-server integrations, internal tooling, and API clients that control both ends of the connection can use QUERY today, exactly as .NET 10 and curl already demonstrate — but it is a reason to treat "the RFC published" and "the ecosystem is ready" as two different milestones, separated by however long it takes the rest of the internet's infrastructure to individually catch up.
HTTP QUERY doesn't replace GET or POST. It fills a gap between them: a standardized way to send potentially complex query instructions in the request body while explicitly declaring that the operation is safe and idempotent.
That's a narrow, well-defined thing to add to a protocol as conservative as HTTP — and precisely because it's narrow, it's worth using exactly where it fits, and nowhere else. The endpoints where a query has outgrown a URL, and where being retryable and cacheable actually matters, finally have a real HTTP-native way to say so. Everything else can, and should, keep doing exactly what GET and POST have always done.
The most useful way to sit with a piece of news like this is the way the RFC itself was written — deliberately, over five years and fourteen drafts, checked against the two decades of precedent WebDAV had already accumulated, and published only once the working group was confident it was solving the actual problem rather than a fashionable-sounding one. QUERY earning a place in the HTTP method registry doesn't obligate anyone to use it tomorrow. It just means that the next time a request genuinely needs to be both a query and something more than a URL can hold, there's finally a correct, standardized answer to reach for — instead of another workaround to build and eventually explain to the next engineer who asks why the search endpoint is a POST.
