gRPC vs REST, byte by byte

The same call, "get order 8120", made three ways: REST with JSON over HTTP/1.1, the same REST call over HTTP/2, and gRPC with Protocol Buffers. Every byte below comes from real encoders for each format, so you can see which savings come from HTTP/2 and which from protobuf.

REST/HTTP 1.1
1,087 B
REST/HTTP 2
944 B
gRPC
550 B
Items in the order
Call on the connection
Inspect
Bytes for the request and response together
1,087
944
550

headersframingbodyHTTP messages only, before TLS and TCP/IP overhead

gRPC, protobuf over HTTP/2, call 1
HeaderEncoded asBytes
request :method: POSTindexed, entry 31
request :scheme: httpsindexed, entry 71
request :path: /shop.v1.Orders/GetOrderliteral, name from entry 4, Huffman20
request :authority: api.shop.exampleliteral, name from entry 1, Huffman14
request content-type: application/grpcliteral, name from entry 31, Huffman13
request te: trailersnew name and value, Huffman11
request grpc-timeout: 1Snew name and value, Huffman14
request user-agent: grpc-go/1.75.0literal, name from entry 58, Huffman12
request authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZC…literal, name from entry 23, Huffman166
response :status: 200indexed, entry 81
response content-type: application/grpcliteral, name from entry 31, Huffman13
trailer grpc-status: 0new name and value, Huffman12

HPACK: entries 1 to 61 are the static table, 62 and up the dynamic table this connection has built. On the first call almost everything is new; pick a later call to see it collapse to indexes.

Where gRPC's 550 bytes go
request: HEADERS frame header
9
request: HPACK header block
252
request: DATA frame header
9
request: gRPC message prefix
5
request: protobuf request
3
response: HEADERS frame header
9
response: HPACK header block
14
response: DATA frame header
9
response: gRPC message prefix
5
response: protobuf body
214
response: trailers HEADERS frame header
9
response: HPACK trailers (grpc-status)
12
Compared with REST over HTTP/1.1
REST/HTTP 2
87% of the bytes
gRPC
51% of the bytes

How to use the simulator

Each bar is one call: the request (with an authorization token, as most APIs need) and the response carrying an order with the chosen number of line items. Headers are blue-grey, framing amber, the body green. Choose a protocol to inspect every header field or the body bytes.

  • Compare the first call with the second. HTTP/1.1 sends the same headers again; HTTP/2's HPACK has stored them and sends one-byte references.
  • Switch from 1 item to 100. Headers stop mattering and the body decides: protobuf stays well under JSON.
  • Inspect the gRPC body to see how protobuf lays out fields: a tag byte, then a varint or a length and the bytes.

The HPACK encoder produces byte-for-byte the same output as the Python hpack library when both always use Huffman coding (this page uses Huffman only when it is shorter, as Go and nghttp2 do), and the protobuf encoder matches Google's protobuf runtime for these messages.

What are gRPC and REST?

REST (representational state transfer) is the architectural style Roy Fielding described in his 2000 dissertation; in everyday use, a "REST API" means resources at URLs, manipulated with HTTP methods, with bodies usually in JSON. gRPC is a remote procedure call framework released by Google in 2015: services and messages are declared in a .proto file, code generators produce typed clients and servers, and calls travel as HTTP/2 requests with Protocol Buffers bodies.

Comparisons often mix up three separate things: the transport (HTTP/1.1 or HTTP/2), the payload format (JSON or protobuf), and the programming model (resources or procedures). The simulator separates the first two by adding a middle case, REST over HTTP/2, which is what most REST APIs actually use today behind a modern load balancer or CDN.

Where the bytes go

Bytes per call, request plus response, for an order with 1, 10 or 100 line items, on the first call of a connection and on a later one:

ItemsREST/HTTP 1.1, firstlaterREST/HTTP 2, firstlatergRPC, firstlater
1617617474221378112
101,0871,087944691550284
1005,7655,7655,6215,3682,2601,994

For small messages the headers are most of the cost, and HTTP/2 removes nearly all of it after the first call, whether the body is JSON or protobuf. For large messages the body dominates, and protobuf's compact encoding is what matters. TLS records, TCP and IP headers and connection setup come on top of these figures and are the same for every column.

Headers: text and HPACK

An HTTP/1.1 request is text: a request line, then one Name: value line per header, each ending in CR LF, and the same headers again on every request. The request and response headers in this example come to 434 bytes per call, and authorization tokens make that larger in practice.

HTTP/2 sends headers in HEADERS frames, compressed with HPACK. Each field is either an index into a table, or a literal that may be added to the table. The static table holds 61 common fields such as :method GET and content-type (name only). The dynamic table, 4,096 bytes by default and separate for each direction, holds fields sent earlier on the connection. Strings can be Huffman-coded with a fixed code tuned to header text. Here the REST headers take 264 bytes on the first call and 11 bytes afterwards, and gRPC's take 278 and 12. HPACK replaced the DEFLATE compression that SPDY used for headers after the CRIME attack showed it could leak secrets such as cookies one character at a time; with HPACK an attacker's guess has to match an entire header value.

Bodies: JSON and protobuf

Protocol Buffers write each field as a tag, the field number shifted left three bits plus a wire type, followed by the value: a variable-length integer (7 bits per byte, high bit set if more bytes follow), or a length and that many bytes for strings and nested messages. Field names never appear, and proto3 omits fields equal to their default. The order body, raw and gzipped:

ItemsJSONJSON, gzipProtobufProtobuf, gzip
11831664263
10653252214148
1005,3308581,924673

At 100 items protobuf is 36% of the JSON size. Gzip removes most of JSON's repeated field names, so compressed the two end up much closer (858 and 673 bytes), and for small messages gzip's own 18 bytes of header and trailer make things worse. The larger practical difference is often CPU: parsing text into numbers and maps costs more than reading varints into a generated struct.

The gRPC wire format

A gRPC call is an HTTP/2 POST to /package.Service/Method with content-type: application/grpc and te: trailers. Each message in either direction is a length-prefixed message: one byte saying whether it is compressed and four bytes of length, then the protobuf. The response starts with a HEADERS frame (:status 200), carries the messages in DATA frames, and ends with a second HEADERS frame of trailers holding grpc-status and, on error, grpc-message. A call that fails immediately can send a single "trailers-only" HEADERS frame. Deadlines travel as grpc-timeout, for example 1S, so every server along the way knows how long the caller will wait.

Errors, deadlines and streaming

REST reports outcomes with HTTP status codes. gRPC uses its own 17 codes in the trailer, from 0 (OK) to 16 (UNAUTHENTICATED), and usually sends HTTP 200 even for errors. If a gRPC client gets a response without grpc-status, for instance an error page from a proxy, it falls back to the mapping in the gRPC documentation:

HTTP statusgRPC status
400 Bad RequestINTERNAL
401 UnauthorizedUNAUTHENTICATED
403 ForbiddenPERMISSION_DENIED
404 Not FoundUNIMPLEMENTED
429, 502, 503, 504UNAVAILABLE
Anything elseUNKNOWN

gRPC has four call shapes: unary, server streaming, client streaming and bidirectional streaming, all on one HTTP/2 stream each. REST calls are unary; streaming needs Server-Sent Events, WebSockets or chunked responses alongside the API.

Choosing

  • REST with JSON suits public APIs and browsers: every language and tool speaks it, responses are readable with curl, and HTTP caching, CDNs and proxies understand it.
  • gRPC suits service-to-service calls inside a system you control: a typed contract with generated clients, streaming, deadlines that propagate, smaller payloads and cheaper parsing. It needs HTTP/2 end to end, a proxy for browsers, and tooling to read binary payloads.

Many systems use both, often generating the REST API from the same .proto files with gRPC-JSON transcoding.

History

Roy Fielding defined REST in his 2000 doctoral dissertation, drawing on his work on HTTP/1.1. Google developed Protocol Buffers internally from 2001 and open-sourced them in 2008, and used an RPC system called Stubby between its services. HTTP/2 was standardized in 2015 (RFC 7540, revised as RFC 9113 in 2022) along with HPACK (RFC 7541), and Google released gRPC the same year as an open successor to Stubby built on it. gRPC joined the Cloud Native Computing Foundation in 2017.

gRPC vs REST questions and answers

Is gRPC faster than REST?

It usually sends fewer bytes and parses faster, but most of the difference comes from two separate choices: HTTP/2 instead of HTTP/1.1, and Protocol Buffers instead of JSON. On this page, a later call for a 10-item order is 1087 bytes as REST over HTTP/1.1, 691 bytes as REST over HTTP/2 and 284 bytes as gRPC. For a small number of calls the network round trip dominates both.

Why are gRPC messages smaller than JSON?

Protocol Buffers identify fields by a small number instead of a name, write integers as variable-length binary rather than decimal text, and skip fields with default values. The 10-item order here is 653 bytes of JSON and 214 bytes of protobuf. Compression narrows the gap: gzipped, they are 252 and 148 bytes.

Can a browser call a gRPC service?

Not directly. Browser JavaScript cannot read HTTP/2 trailers or control framing, and gRPC carries its status in trailers. gRPC-Web, and protocols such as Connect, use a proxy or a server that speaks a browser-friendly variant; REST with JSON remains the simpler choice for public browser APIs.

How does gRPC report errors?

With a numeric status in the grpc-status trailer (0 is OK, then 16 error codes such as NOT_FOUND, DEADLINE_EXCEEDED and UNAVAILABLE) and an optional grpc-message, while the HTTP status is usually 200. REST uses the HTTP status code itself. When a gRPC client gets a response with no grpc-status, for example from a proxy, it maps the HTTP status: 503 becomes UNAVAILABLE and 404 becomes UNIMPLEMENTED.

What is HPACK?

The header compression format of HTTP/2 (RFC 7541). It refers to common headers by an index into a 61-entry static table, Huffman-codes strings, and keeps a dynamic table per connection so that a header sent before can be sent again as a single index. The REST request and response headers here take 434 bytes as HTTP/1.1 text on every call, and 264 bytes with HPACK on the first call and 11 bytes on later ones.

What are the four kinds of gRPC call?

Unary (one request, one response), server streaming (one request, a stream of responses), client streaming (a stream of requests, one response) and bidirectional streaming, where both sides send independently on one HTTP/2 stream. REST maps to unary calls; streaming over REST needs Server-Sent Events, WebSockets or chunked responses.

Further reading

Published Updated