REST vs gRPC: Which One Should You Use?
REST and gRPC are two ways for one program to ask another for something. We built one tea-shop stock service with both doors and measured them for real: answer size, speed over 10,000 calls, streaming, what happens when a field is renamed or renumbered, and why browsers need gRPC-Web. Then which one to pick.

Two ways to ask one question
Your app needs to ask another service a question, and someone says: use gRPC, it's faster than REST. Here's Ana's tea shop, asking its stock service how much Sencha is left. Over REST: a plain address, and an answer you can read. The same question to the gRPC port, with the same curl: an error.
(.venv) ana@laptop:~/order-service$ curl -s stock:8000/teas/sencha{"name":"Sencha","stock":12,"cents":650}
(.venv) ana@laptop:~/order-service$ curl -sS stock:50051/teas/senchacurl: (1) Received HTTP/0.9 when not allowed
Both are ways for one program to ask another for something. REST sends a request to an address and gets text back. gRPC calls a function on the other machine, using a contract both sides share. By the end you'll know which one to use, how much faster gRPC really was in our test, and why one tiny edit made it report zero tea in stock.
Someone asked exactly this on Stack Overflow in 2017: REST versus gRPC, when should I choose one over the other? People still ask it, so let's answer it with a real run, one question at a time.

How REST asks
What does REST actually do? It treats each thing as a resource with an address: this tea lives at slash teas slash sencha. You ask with an HTTP verb, GET to read, POST to create, and the answer usually comes back as JSON, plain text.

In Ana's order service that's three lines of Python: get the address, read the JSON, print the stock. Twelve in stock.
(.venv) ana@laptop:~/order-service$ cat check_rest.py# Ana's order service asks the stock service over REST: a plain HTTP GET that answers JSON. import httpx tea = httpx.get("http://stock:8000/teas/sencha").json() print(f"{tea['name']}: {tea['stock']} in stock")
(.venv) ana@laptop:~/order-service$ python3 check_rest.pySencha: 12 in stock
Look at what came back. Every answer spells out its field names: name, stock, cents, every single time. Even the twelve travels as two text characters. That's why JSON is easy to read, and it's where the bytes go.

How gRPC asks
gRPC starts somewhere else: a contract. A file, stock.proto, lists the functions the stock service offers, GetTea and WatchStock, and the exact shape of every message they send.
(.venv) ana@laptop:~/order-service$ cat stock.protosyntax = "proto3"; service Stock { rpc GetTea (TeaRequest) returns (Tea); rpc WatchStock (TeaRequest) returns (stream Tea); } message TeaRequest { string name = 1; } message Tea { string name = 1; int32 stock = 2; int32 cents = 3; }
Each field in that contract has three parts. A type: here, a whole number. A name, which your code uses. And a number, two. That equals sign doesn't set the stock to two: two is the field's number. Remember it, because it's the part that actually travels between the machines.

Then a tool reads the contract and writes code for you: two new Python files. One holds the message shapes; the other holds a stub, a ready-made client. Ana's script calls GetTea on that stub like a normal function, and gets the same answer: twelve.
(.venv) ana@laptop:~/order-service$ python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. stock.proto
(.venv) ana@laptop:~/order-service$ lsbench.py check_grpc.py check_rest.py sizes.py stock.proto stock_pb2.py stock_pb2_grpc.py
(.venv) ana@laptop:~/order-service$ cat check_grpc.py# The same question over gRPC: call GetTea like a function, with code generated from stock.proto. import grpc import stock_pb2 import stock_pb2_grpc stub = stock_pb2_grpc.StockStub(grpc.insecure_channel("stock:50051")) tea = stub.GetTea(stock_pb2.TeaRequest(name="sencha")) print(f"{tea.name}: {tea.stock} in stock")
(.venv) ana@laptop:~/order-service$ python3 check_grpc.pySencha: 12 in stock
Underneath, gRPC sends that call as an HTTP/2 request, the newer version of the web's protocol, and the message as protobuf: compact binary, not text. That's why plain curl failed at the start. It spoke the older HTTP/1.1, and got back bytes it couldn't read as an HTTP reply.

To try a gRPC service by hand, you need a tool that knows the contract, like grpcurl. Give it the proto file, and it shows the answer as JSON, for humans. And the reply's content type? Application slash g r p c.
(.venv) ana@laptop:~/order-service$ grpcurl -plaintext -proto stock.proto -d '{"name": "sencha"}' stock:50051 Stock/GetTea{ "name": "Sencha", "stock": 12, "cents": 650 }
(.venv) ana@laptop:~/order-service$ grpcurl -v -plaintext -proto stock.proto -d '{"name": "sencha"}' stock:50051 Stock/GetTea | grep content-typecontent-type: application/grpc
This isn't only for small shops. The Cloud Native Computing Foundation's Netflix case study says a huge part of Netflix's internal service-to-service traffic runs on gRPC. Before that, Netflix ran its own stack on HTTP/1.1, and today all new Java development there starts with gRPC.

Smaller and faster, by how much?
So is it really smaller? Ana's script asks both doors for the same Sencha and counts the bytes. JSON: 40 bytes. Protobuf: 13, about a third.
(.venv) ana@laptop:~/order-service$ python3 sizes.pyJSON: 40 bytes {"name":"Sencha","stock":12,"cents":650} protobuf: 13 bytes 0a 06 53 65 6e 63 68 61 10 0c 18 8a 05
Here are those 13 bytes. The first says field one, text, and the next says six letters follow: Sencha. Then field two: twelve. Then field three: six hundred and fifty. No field names at all, just the numbers from the contract.

Smaller, but is it faster? Ana's script asks each door ten thousand times in a row. Guess first: how much faster is gRPC? About one and a half times. REST took 2.58 seconds, gRPC 1.75.
(.venv) ana@laptop:~/order-service$ python3 bench.py 10000REST: 10,000 calls in 2.58 s (0.26 ms each) gRPC: 10,000 calls in 1.75 s (0.18 ms each)
Per call, that's 0.26 milliseconds against 0.18. Real, but small: across a real network, the trip itself often takes longer than either.

So where does the saving come from? Fewer bytes to send. No text to turn back into numbers: the generated code reads the binary directly. It adds up when services call each other thousands of times a second, and hardly matters for a page a person clicks once.

When the answer keeps changing
Now a harder question: Ana wants to know the moment Sencha runs low. With plain REST you ask again and again, and mostly hear the same answer. gRPC can stream: one call, and the server keeps sending each change.

In a second window, Ana starts WatchStock. The first message arrives at once: twelve. And the call stays open.
(.venv) ana@laptop:~/order-service$ grpcurl -plaintext -proto stock.proto -d '{"name": "sencha"}' stock:50051 Stock/WatchStock{ "name": "Sencha", "stock": 12, "cents": 650 }
Back in her first window, two customers each buy two packs of Sencha, through the REST door. Ten left, then eight.
(.venv) ana@laptop:~/order-service$ curl -s stock:8000/orders -H "Content-Type: application/json" -d '{"tea": "sencha", "qty": 2}'{"ok":true,"tea":"Sencha","left":10}
(.venv) ana@laptop:~/order-service$ curl -s stock:8000/orders -H "Content-Type: application/json" -d '{"tea": "sencha", "qty": 2}'{"ok":true,"tea":"Sencha","left":8}
And the watching window? It got both changes, ten and eight, the moment they happened, without asking again.
(.venv) ana@laptop:~/order-service$ grpcurl -plaintext -proto stock.proto -d '{"name": "sencha"}' stock:50051 Stock/WatchStock{ "name": "Sencha", "stock": 12, "cents": 650 } { "name": "Sencha", "stock": 10, "cents": 650 } { "name": "Sencha", "stock": 8, "cents": 650 }
That stream is one of four kinds of call a gRPC contract can have. One question, one answer, like GetTea. One question, and a stream of answers, like WatchStock. A stream of questions in, say a phone uploading readings. Or streams both ways at once, like a chat.

When the API changes
Every API changes. Ana's team renames the stock field to in stock, on both doors. The field number stays two. But the order service still runs its old code.
(.venv) ana@stock:~/stock-service$ diff stock.proto v2/stock.proto14c14 < int32 stock = 2; --- > int32 in_stock = 2;
(.venv) ana@stock:~/stock-service$ python3 shop_server.py --v2stock service v2 ready: REST on port 8000, gRPC on port 50051
The REST answer now says in stock. Guess first: old clients, new server. Which one still works? Not REST: the JSON says in stock, and Ana's old script asks for stock: a KeyError, and the order service stops.
(.venv) ana@laptop:~/order-service$ curl -s stock:8000/teas/sencha{"name":"Sencha","in_stock":12,"cents":650}
(.venv) ana@laptop:~/order-service$ python3 check_rest.pyTraceback (most recent call last): File "/home/ana/order-service/check_rest.py", line 5, in <module> print(f"{tea['name']}: {tea['stock']} in stock") ~~~^^^^^^^^^ KeyError: 'stock'
The gRPC client doesn't notice. Twelve in stock, with its old code.
(.venv) ana@laptop:~/order-service$ python3 check_grpc.pySencha: 12 in stock
Why? The old client's contract says stock is field two. The new server's says in stock is field two. The bytes on the wire only say: field two, twelve. The name never travels, so both sides still agree.

So what made gRPC say zero? Change the number instead, from two to four, and run the same old client.
(.venv) ana@stock:~/stock-service$ diff stock.proto v3/stock.proto14c14 < int32 stock = 2; --- > int32 in_stock = 4;
Sencha: zero in stock. No error, no warning, just a wrong number that looks right.
(.venv) ana@laptop:~/order-service$ python3 check_grpc.pySencha: 0 in stock
The server now sends field four. The old client looks for field two, finds nothing, and proto3 fills in the default for a missing number: zero. That's why the protobuf guide says a field number can't change once it's in use.

So the two fail differently. REST breaks loudly when a name changes. gRPC survives a rename, but a changed number gives quiet, wrong answers. With either one, add new fields and keep the old ones.

Where gRPC is harder
So why not use gRPC everywhere? The browser. Page code can't reach the raw HTTP/2 frames gRPC needs, so a web page uses gRPC-Web, and a proxy such as Envoy translates in between. REST needs none of that: the browser already speaks it.

It's also harder to look at. A REST answer opens in curl, a browser tab, or any language. A gRPC answer needs the proto file, generated code, or a tool like grpcurl. That's the price of the contract.

Which one to pick
So which one should you use? REST talks in readable JSON, works in every browser, and needs no contract. gRPC talks in compact binary, needs a proto file, and streams out of the box. In one line: REST is addresses and readable JSON; gRPC is typed function calls in binary.

Use REST for anything public: web pages, partners, an API people try with curl. Use gRPC between your own services, when they talk a lot, need streaming, or are written in different languages that share one contract. Many teams run both, like Ana's stock service: REST at the edge, gRPC inside.
Every command and the lab files are linked in the description, so you can run both doors yourself.

Recap
So: JSON was 40 bytes, protobuf 13. gRPC was about 1.5 times faster, it streams, and it needs a contract you never renumber. REST stays the door for browsers.
If this settled the argument for your team, a like helps YouTube show it to more beginners, and subscribing brings the next one to you.
Every request here ran for real between two small Linux machines. Thanks for watching Cloud Explainers.
Sources
- CNCF case study, Netflix ("a huge part of the internal service-to-service communication at Netflix runs on gRPC"; the earlier HTTP/1.1 stack; new Java development starts with gRPC)
- gRPC, Core concepts (unary, server streaming, client streaming and bidirectional streaming RPCs)
- gRPC over HTTP/2, the protocol spec
- gRPC blog, The state of gRPC in the browser (raw HTTP/2 frames are inaccessible in browsers; gRPC-Web and a proxy such as Envoy)
- Protocol Buffers, proto3 language guide (field numbers identify fields in the wire format and can't change once in use; default values)
- Stack Overflow, REST vs gRPC: when should I choose one over the other? (2017)
Demos: two containers on our own Mac standing in for Ana's stock service and order service; the tea shop is made up. Watch the full video on YouTube.