Payloads in HTTP GET Requests: Myths, RFC Specs, and Practical Implementation

Arpit Bhayani

Arpit Bhayani

Apr 04, 2022 • 6 min read

Play

Payloads in HTTP GET Requests: Myths, RFC Specs, and Practical Implementation

A pervasive rule taught to software engineers is: “An HTTP GET request cannot contain a payload body; only POST, PUT, or PATCH requests can carry payloads.”

However, this rule confuses convention with protocol capability. In reality, clients can technically transmit a payload in an HTTP GET request, and web servers can parse and process that payload. While there are legitimate operational reasons why you typically should avoid doing so, the HTTP specification does not forbid it.


1. Deconstructing the Myth: “Can” vs. “Should”

In standard system architectures, an HTTP client issues a GET request to retrieve data from an API server, which maps the request to database records or internal services:

GET /users/1729 HTTP/1.1
Host: api.example.com
+--------+   HTTP GET /users/1729   +------------+   SQL Query   +----------+
| Client | -----------------------> | API Server | ------------> | Database |
|        | <----------------------- |            | <------------ |          |
+--------+    200 OK (User Data)    +------------+    Result     +----------+

When developers need to pass filtering or retrieval criteria, they almost universally rely on URL path parameters or query strings (e.g., ?status=active&sort=desc).

When asked why we don’t send a JSON payload in a GET body, the standard answer is usually “the HTTP specification forbids it.” That assertion is historically inaccurate.


2. What the HTTP/1.1 Specifications Actually Say

The Internet Engineering Task Force (IETF) publishes RFCs (Request for Comments) that define internet standards. The treatment of GET payloads has evolved across different versions of the HTTP specification:

RFC 2616 (June 1999)

Under the legacy RFC 2616, section 4.3 stated:

“…if the request method does not include defined semantics for an entity-body, then the message-body SHOULD be ignored when handling the request.”

Because GET was not given explicit entity-body semantics, servers and intermediate gateways were guided to ignore request bodies on GET requests.

RFC 7231 (June 2014)

When RFC 7231 obsoleted RFC 2616, the guidance around GET method semantics was updated under Section 4.3.1 (GET):

“A payload within a GET request message has no defined semantics; sending a payload body on a GET request might cause some existing implementations to reject the request.”

The strict phrase instructing implementations to ignore the body was removed. Instead, the modern RFC clarifies:

  1. No Defined Semantics: The protocol itself assigns no default behavioral meaning to a GET payload (unlike POST, where the payload represents an entity to be processed or appended).
  2. Compatibility Warning: Sending a payload does not violate the protocol grammar, but older or strict implementations (firewalls, reverse proxies, caches) might reject, sanitize, or drop the request.

The protocol specification leaves handling up to the server implementation.


3. Practical Demonstration: Receiving a GET Body in Flask

Because HTTP operates on top of a TCP stream, an HTTP server simply reads bytes from the socket until it parses the headers (\r\n\r ) and checks the Content-Length or Transfer-Encoding header to determine how many bytes of body remain to be read.

A standard Python web framework like Flask demonstrates that web servers do not inherently drop GET bodies:

from flask import Flask, request

app = Flask(__name__)

@app.route("/search", methods=["GET"])
def search():
    # Accessing the raw payload body
    raw_payload = request.data.decode("utf-8")
    
    return {
        "status": "success",
        "method": request.method,
        "received_payload": raw_payload
    }

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)

Executing the Request via cURL

You can send arbitrary text or JSON inside an HTTP GET using curl’s --data flag:

curl -X GET http://localhost:5000/search \
  -H "Content-Type: text/plain" \
  --data "hello world"

Server Response

{
  "method": "GET",
  "received_payload": "hello world",
  "status": "success"
}

The server receives the body, extracts the bytes, and executes the route logic without errors.


4. Real-World Case Study: Elasticsearch

The most prominent real-world example of using payloads in GET requests is Elasticsearch (notably in versions 5.x and prior, with continued backward compatibility).

The Problem Elasticsearch Faced

Elasticsearch queries rely on the Query DSL—a deep, complex JSON structure supporting boolean filters, nested clauses (must, should, must_not), scoring algorithms, aggregations, and runtime script evaluations.

{
  "query": {
    "bool": {
      "must": [
        { "match": { "title": "distributed systems" } }
      ],
      "filter": [
        { "term": { "status": "published" } },
        { "range": { "publish_date": { "gte": "2023-01-01" } } }
      ]
    }
  }
}

Encoding a query like this into URL query parameters creates significant issues:

  • URL Length Limits: Browsers, intermediate proxies, load balancers, and web servers enforce limits on URL length (often 2KB to 8KB).
  • Readability and Encoding: Serializing nested object trees into percent-encoded query parameters produces fragile, unmaintainable strings.

Why Elasticsearch Used GET Instead of POST

Semantically, an Elasticsearch query is read-only and idempotent. It does not alter database state, create new resources, or trigger side effects. In RESTful conventions, read-only requests belong on GET.

Because of this semantic match, Elasticsearch accepted search queries sent via GET with the JSON payload in the request body:

GET /my_index/_search HTTP/1.1
Host: localhost:9200
Content-Type: application/json

{
  "query": {
    "match_all": {}
  }
}

To accommodate clients, libraries, and proxies that reject or strip payloads from GET requests, Elasticsearch later adopted dual-method support, allowing users to issue identical searches using POST /my_index/_search.


5. Why You Generally Should Not Send Payloads in GET Requests

While the protocol technically permits payloads in GET requests, production systems generally avoid them due to architectural and infrastructure constraints:

ConcernExplanation
Caching Proxies & CDNsHTTP caches (e.g., Varnish, Squid, Cloudflare) use the HTTP method and URI as the primary cache key. Most ignore the request body when evaluating cache hits for a GET request, potentially returning cached results meant for completely different query payloads.
Intermediate GatewaysCorporate firewalls, API gateways, and older reverse proxies may strip the body of a GET request before routing it upstream, or reject it outright with a 400 Bad Request or 405 Method Not Allowed.
Browser SupportStandard client APIs (like native HTML forms) do not support assigning payloads to GET requests, and some browser fetch() implementations historically threw errors when a body was attached to a GET request.
Semantic AmbiguityBecause the RFC states that GET payloads have no defined semantics, downstream systems cannot safely assume how the payload influences the resource representation.

6. Summary and Architectural Guidelines

  • You can send a body in a GET request: The HTTP/1.1 standard (RFC 7231) removes legacy restrictions instructing servers to ignore GET payloads.
  • Servers can interpret it: Web application servers (Flask, Express, Go standard library, Netty) can read and process GET payloads if written to do so.
  • Avoid it unless strictly necessary: Due to edge caches, intermediate proxies, and client-side tool limitations, sending payloads in GET requests can lead to subtle routing, security, and caching bugs.
  • The Pragmatic Workaround: When read-only queries exceed URL size limits or require complex hierarchical formats (like JSON), use a POST request to an explicit action endpoint (e.g., POST /search or POST /query), documenting that the endpoint operates idempotently without side effects.
Arpit Bhayani

Principal Engineer II at Razorpay - building Agent Studio, Ex-staff engg at GCP Memorystore & Dataproc, Creator of DiceDB, ex-Amazon Fast Data, ex-Director of Engg. SRE and Data Engineering at Unacademy. I spark engineering curiosity through my no-fluff engineering videos on YouTube and my courses