Beyond GET: What HTTP QUERY (RFC 10008) Could Mean for Liferay's Headless APIs
This piece is a technical follow-up to a post by Laxit Khanpara on Liferay.dev, which first raised the question of whether the newly standardized HTTP QUERY method has a place in Liferay’s Headless APIs. That post makes the case in broad strokes; here, we dig into what RFC 10008 actually specifies and work through where QUERY would — and wouldn’t — fit into a Headless REST API like Liferay’s today.
The problem: search outgrows the URL
A plain GET is fine for simple retrieval. It falls apart the moment real applications need it: dynamic filters, nested conditions, relationship queries across objects, multiple sort orders, pagination, field selection, and — increasingly — search criteria generated by an LLM rather than typed by a person. Liferay’s Headless APIs already support rich OData-style filtering through query parameters, and it works, but it turns into an unmaintainable, hard-to-cache, awkward-to-log URL the moment the criteria get non-trivial:
GET /o/c/employees?filter=(department eq 'IT' and salary gt 50000)
The common workaround is a dedicated POST “search” endpoint that accepts the same criteria as a JSON body instead of a query string:
POST /o/c/employees/search
Content-Type: application/json
{
"department": "IT",
"salary": { "gt": 50000 },
"experience": { "gte": 5 }
}
That’s more ergonomic, but it’s semantically wrong. POST means “create or process a resource.” Searching does neither — you’re not creating an employee record by asking which employees match a filter — and that mismatch is exactly why request caching, safe-retry, and idempotency guarantees don’t apply to a POST search the way they do to GET.
What RFC 10008 actually specifies
RFC 10008, The HTTP QUERY Method, was published in June 2026 (authors: J. Reschke, J.M. Snell, and M. Bishop) as a Proposed Standard. It defines a new HTTP method, QUERY, that closes exactly this gap: a request that is safe and idempotent like GET, but that carries a request body like POST.
A few specifics that matter for API design, straight from the spec:
- Safe and idempotent. Because a
QUERYrequest can’t change server state, clients and intermediaries can retry it freely without worrying about partial or duplicate effects — the same guaranteeGETgives you today. - Explicitly cacheable. Unlike
POST,QUERYresponses are cacheable by design — but since the request body is part of what determines the response, the cache key has to include the body, not just the URI. - A new
Accept-Queryresponse header. A resource can advertise that it supportsQUERY, and which media types it accepts for the query body, via this header — letting clients discover the capability instead of guessing.
In practice, the same search from above becomes:
QUERY /o/c/employees
Content-Type: application/json
{
"department": "IT",
"salary": { "gt": 50000 }
}
Same intent as the POST /search version, same read-only guarantee as GET — without either one’s downside.
Where this fits into Liferay’s Headless APIs
The original post lays out several places this would matter for Liferay specifically, and they hold up:
Structured search requests. Filter, sort, and pagination all become fields in one JSON body instead of a hand-assembled OData filter string:
{
"filter": { "department": "IT", "salary": { "gt": 50000 } },
"sort": [{ "field": "name", "direction": "ASC" }],
"page": 1,
"pageSize": 20
}
Dynamic Objects. Liferay’s Object framework lets administrators define entirely new object types at runtime — which means the set of filterable fields isn’t known at development time either. A structured JSON body is a far better fit for that than composing filter-string syntax dynamically.
Relationship queries. Filtering across a related object, like matching employees whose department has a given name, reads naturally as nested JSON — { "department": { "name": "Engineering" } } — where the equivalent OData expression gets unwieldy fast.
Low-code tooling. Low-code builders already generate JSON internally to represent a user’s filter selections. QUERY removes the translation layer that currently converts that JSON into a filter string just to satisfy GET.
AI-generated queries. LLMs produce structured JSON far more reliably than they produce correctly-escaped OData filter strings, which matters as more search requests originate from an AI agent instead of a person clicking through a UI.
Request validation. A JSON body can be validated against a JSON Schema before it’s ever executed — something you can’t cleanly do to a query string.
The catch: it’s brand new
RFC 10008 is a few months old at the time of writing, and adoption follows standards, not the other way around. Frameworks in Liferay’s own stack — Spring Boot, Jakarta REST — don’t support the QUERY method yet, which means there’s no realistic path to it showing up in Liferay’s Headless APIs until that tooling catches up.
Takeaway
Nothing here argues for replacing GET — simple lookups should stay exactly as simple as they are today. The interesting case is the search endpoints that already outgrew a query string and quietly became a semantically-wrong POST. For those, QUERY is a real fix, not a workaround: same safety and cacheability guarantees developers already rely on with GET, applied to requests that need a body to express what they’re asking for. Whether it lands in Liferay’s Headless platform is, for now, a framework-support question rather than a design one — but it’s worth knowing the shape of the answer before it does.
This article expands on Laxit Khanpara’s original post — read it on Liferay.dev for the author’s own framing, or read RFC 10008 itself for the full specification.
This article is adapted from: Laxit Khanpara, Liferay.dev