Deep Dive - JSON vs gRPC
This post will explore gRPC and Protocol Buffers. We will look at how moving from text to binary serialization can reduce your bandwidth usage by 50% and your CPU usage by 90%.
In the web development world JSON is the king. It is human readable and flexible and universally supported. Every boot camp and tutorial teaches you to build APIs using JSON over HTTP.
But inside high scale companies like Uber Netflix and Google JSON is often forbidden for internal service to service communication.
Why would they ban the most popular format in the world?
Because JSON is expensive. It is a text based format in a binary world. When you have 500 microservices talking to each other millions of times per second the cost of parsing text brackets and quotes becomes a massive tax on your CPU and network.
This post will explore gRPC and Protocol Buffers. We will look at how moving from text to binary serialization can reduce your bandwidth usage by 50% and your CPU usage by 90% and why the physics of parsing determines the speed of your architecture.
The Cost of Text Serialization
To a computer text is the worst possible format for data.
Imagine you want to send the number 123456 to another service.
In Binary (Computer Native) - This is a 4 byte integer. It is a raw sequence of 32 bits. The CPU understands it instantly.
In JSON (Text) - This is a string of 6 characters
"1" "2" "3" "4" "5" "6". Each character takes 1 byte. So it takes 6 bytes.
But it gets worse. To make it valid JSON you have to wrap it in an object with a field name. {"id": 123456}
Now we are sending brackets quotes and the field name id. The receiver cannot just read the memory. It has to Parse it.
Scanning - It must scan bytes one by one looking for
{and"and:.String Matching - It must compare
"id"against known keys.Conversion - It must convert the characters
"123456"into the integer123456using multiplication and addition loops(1*100000 + 2*10000 + ...)while validating UTF-8 encoding.
In a dense microservice architecture services often spend more CPU cycles parsing JSON than they spend executing actual business logic.
The Binary Solution (Protocol Buffers)
Google created Protocol Buffers (Protobuf) to solve this. Protobuf is a binary serialization format. It strips away all the human readability to optimize for machine readability.
The Wire Format Anatomy
In JSON you send the field name username in every single record. In Protobuf you send a Tag.
The Tag is a single byte (usually) generated by the formula
Tag = (Field_Numbr « 3) | Wire_Type
Field Number - The unique ID from your
.protofile (e.g.,1).Wire Type - Tells the parser how to read the next chunk (e.g.,
0for Varint,2for Length Delimited string).
Instead of sending {"username": "Alice"}, Protobuf sends
Tag -
0A(Field 1, Wire Type 2).Length -
05(5 bytes follow).Value -
41 6C 69 63 65(”Alice” in ASCII).
A total of 7 bytes. No quotes, no brackets, no field names.
The Magic of Varints (Variable Integers)
Protobuf uses a clever trick called Varints to compress numbers.
In a standard database an int32 always takes 4 bytes even if the number is just 5. In Protobuf small numbers take less space.
The Logic
It uses the Most Significant Bit (MSB) of each byte as a continuation flag.
1- “There are more bytes coming for this integer.”0- “This is the last byte.”
For example, encoding the number 300
Binary -
100101100Group into 7-bit chunks -
0000010(MSB) and0101100(LSB).Reverse order (Little Endian) and add flags
Byte 1 -
1+0101100=10101100(0xAC) -> MSB is 1 (continue).Byte 2 -
0+0000010=00000010(0x02) -> MSB is 0 (stop).
The result, AC 02. (2 bytes instead of 4).
ZigZag Encoding
Standard binary has a problem with negative numbers. A small negative number like -1 is usually represented as a huge string of 11111111... in Two’s Complement notation (all 32 or 64 bits set to 1). This ruins the Varint optimization because it looks like a “large” number.
Protobuf uses ZigZag encoding to map signed integers to unsigned integers.
0->0-1->11->2-2->3
This allows Varints to compress small negative numbers into just 1 byte efficiently.
The Transport (gRPC and HTTP 2)
Protobuf is just the data format. gRPC (Google Remote Procedure Call) is the transport system that moves that data. While REST usually relies on HTTP 1.1 gRPC was built exclusively for HTTP 2.
Binary Framing and Streams
HTTP 1.1 sends plain text headers and bodies. HTTP 2 slices data into binary Frames.
HEADERS Frame - Contains compressed metadata.
DATA Frame - Contains the actual Protobuf payload.
gRPC assigns every request a Stream ID.
Request 1 (Stream ID 1) -
[HEADERS] [DATA] ...Request 2 (Stream ID 3) -
[HEADERS] [DATA] ...
These frames are interleaved on the same TCP connection. This is Multiplexing.
The “Head of Line Blocking” problem of HTTP 1.1 (where one slow request blocks all others) is eliminated because a slow processing Frame for Stream 1 does not block the Frame for Stream 3.
Header Compression (HPACK)
In REST every request sends massive headers like User-Agent and Authorization repeatedly.
HTTP 2 uses HPACK to compress headers. The client and server both maintain a state table of previously seen headers. If you send the same User-Agent twice the second request just sends a tiny reference index (e.g., 0x42) saying “Use header #66 from the table.”
The Contract (Interface Definition Language)
The biggest cultural shift with gRPC is the strict contract.
In JSON REST you can just add a field or change a string to an integer and hope the receiver handles it. This leads to production crashes.
In gRPC the .proto file is the absolute source of truth. You use a compiler (protoc) to generate the actual code for your application.
It generates the Java class for the backend.
It generates the Go struct for the client.
It generates the TypeScript type for the dashboard.
This guarantees Type Safety across languages. If you change the schema you have to recompile. You catch breaking changes at Compile Time not at Runtime when the customer clicks a button.
Streaming Capabilities
Because gRPC is built on HTTP/2 streams it supports modes that REST cannot easily do
Unary - Standard Request -> Response.
Server Streaming - Client sends one request, Server sends a stream of 100 updates (e.g., Stock Ticker).
Client Streaming - Client uploads a massive file in chunks, Server sends one “OK”.
Bidirectional Streaming - Both sides send text messages in a Chat App simultaneously.
Conclusion
We are not saying you should kill JSON. JSON is perfect for External APIs.
Public APIs
If you are building an API for the public (like Stripe or Twitter) use JSON. It is easy for strangers to read and debug via curl.
The Browser
Browsers speak JSON natively. gRPC support in browsers (gRPC Web) requires a proxy and is heavier than fetch.
The Internal Rule
However for Internal Traffic (Service A calling Service B inside your AWS VPC) you should use gRPC.
The combination of Varint compression, HPACK, and HTTP/2 Multiplexing creates a communication layer that is mathematically superior to REST/JSON.
JSON is for humans. Protobuf is for machines. Inside your data center the machines are doing the talking.


