$ cd ../blog

Why We're Adopting the New HTTP QUERY Method for Complex Searches

·5 min read

We're planning to adopt the new HTTP QUERY method in our APIs, and the reason is one of those problems every backend team eventually meets: some of our complex search requests are starting to hit URL length limitations with GET.

If you've built serious filtering or search endpoints, you know exactly how this story goes — and why the existing options were always a compromise.

The corner GET paints you into

REST's promise was clean: reads are GET, and GET carries its parameters in the URL. That works beautifully right up until your product grows real search. Multi-field filters, nested conditions, sort chains, column selections, date ranges — serialized into a query string, a realistic search from our UI can produce URLs thousands of characters long.

And URLs have limits. Not one clean limit, either — a fuzzy, unreliable patchwork of them: servers commonly cap request lines around 8KB, some proxies and CDNs clamp earlier, and some tooling misbehaves long before that. Worse, the failure isn't graceful: a filter combination that works in staging gets truncated by an intermediary in production, and you're debugging a 414 — or a silently wrong result — that only happens for power users with ambitious filters.

Query strings are also just a miserable format for structured data. Encoding nested boolean logic into ?filter[or][0][and][1][field]=status is nobody's idea of expressive, and every team invents its own dialect of that misery — then documents it, debugs it, and apologizes for it.

The workaround we all use, and why it lies

So every team reaches for the same workaround: POST /search with a JSON body. Structured, unlimited, readable. Problem solved?

Not quite — because POST lies about semantics. POST tells every intermediary, cache, and client library: "this request changes something; treat it as dangerous." But a search changes nothing. The mismatch has real costs:

  • Caching breaks. HTTP caches won't cache POST responses meaningfully — you forfeit the entire caching layer for your read-heaviest endpoints.
  • Retries get scary. Clients and proxies retry safe methods freely; failed POSTs can't be retried automatically because POST promises nothing about side effects.
  • Tooling loses information. Monitoring, gateways, and security scanners all treat your innocent search as a write. Semantic information that could drive smart behavior is simply gone.

And no, sending a body with GET isn't the escape hatch. The spec technically doesn't forbid it, but it defines no semantics for it — which means proxies, caches, and servers are free to drop it, and some do. GET-with-body is the worst of all options: it looks clever locally and fails somewhere in the middle of the internet.

We've lived with this lie so long it feels normal. It isn't — it's a gap in the protocol's vocabulary.

QUERY: the missing verb

The HTTP QUERY method — standardized by the IETF after years as a draft — closes the gap directly. QUERY brings together the best of both worlds:

  • Request body support, like POST. Send your search criteria as structured JSON in the body. No length ceiling, no encoding gymnastics.
  • Safe, read-only semantics, like GET. QUERY is defined as safe and idempotent. Intermediaries may retry it, caches may cache it (the response can be keyed on a digest of the body), and every layer of the stack knows no state changed.

A complex search finally gets to say what it actually is: a read with a payload.

QUERY /products HTTP/1.1
Content-Type: application/json

{
  "filter": {
    "and": [
      { "category": { "in": ["electronics", "accessories"] } },
      { "price": { "lt": 500 } },
      { "or": [ { "inStock": true }, { "preorder": true } ] }
    ]
  },
  "sort": ["-popularity", "price"],
  "fields": ["id", "name", "price"]
}

It reads like what it is. That's the whole pitch.

Adopting it without betting the farm

Ecosystem support is the honest caveat — new HTTP methods propagate slowly through frameworks, proxies, CDNs, and client libraries, and QUERY is no exception. Our adoption plan is deliberately boring:

  • Start at the edges we control. Internal service-to-service search APIs first, where we own both client and server and can verify the whole path handles QUERY cleanly.
  • Keep a POST fallback. The handler logic is shared; the route accepts both methods during the transition. Clients upgrade when their stack allows.
  • Verify intermediaries early. Load balancers, CDN, API gateway — each needs a quick test that unknown-method requests pass through intact. Most modern infrastructure forwards them fine; "most" is not "all."
  • Watch the caching win. The genuinely exciting part. Cacheable complex searches could take real load off our read path — that's the metric that will justify the migration.

A small sign of a healthy protocol

Beyond our specific problem, I find QUERY quietly encouraging. HTTP is thirty-plus years old and still growing vocabulary to name things developers actually do. The gap between "reads with complex parameters" and the GET/POST dichotomy existed for decades, papered over by a million pragmatic POST /search endpoints. Now the semantics can finally be honest.

It feels like a cleaner, more expressive approach for complex read operations, and I'm excited to see how the ecosystem adopts it.

So, the question I asked on LinkedIn, extended here: are you planning to adopt HTTP QUERY early, or waiting until framework support matures? Either answer is defensible — but if your search URLs are measured in kilobytes, you already know which camp you're drifting toward.