What Are Protocol Buffers? Protobuf Explained
Protocol Buffers (protobuf) are a binary serialization format with strict schemas — smaller and faster to parse than JSON. How it works.
Protocol Buffers, or protobuf, are Google’s language-neutral binary serialization format: you define a message’s fields in a .proto schema file, a compiler generates typed code for your language, and messages are encoded as compact binary blobs instead of text. The payoff over formats like JSON is size and parse speed — at the cost of human readability and a required schema.
How a .proto schema works
A .proto file declares message types and their fields, each with a type and a unique field number:
message User {
int32 id = 1;
string name = 2;
string email = 3;
}
The field numbers, not the names, are what actually go on the wire. Run the protobuf compiler (protoc) against this file and it generates a class or struct in your target language — Go, Python, Java, TypeScript, and more — with getters, setters, and serialization methods already written. You never hand-write a parser.
Encoding: binary and compact
A JSON payload spells out every key as a string on every message: {"id": 42, "name": "Ana"}. Protobuf instead writes each field as a tag (derived from the field number and wire type) followed by its value, with no field names and no punctuation. Integers use variable-length encoding, so small numbers take fewer bytes. The result is typically a fraction of the size of the equivalent JSON, and decoding is a matter of reading tagged bytes rather than parsing text — no tokenizer, no string allocation for keys.
The trade-off is that a protobuf message is unreadable without its schema. You can’t open one in a text editor and understand it the way you can with JSON; you need the .proto file (or a tool that has it) to make sense of the bytes.
Protobuf vs JSON
| Protobuf | JSON | |
|---|---|---|
| Format | Binary | Text |
| Schema | Required (.proto file) | Optional (JSON Schema) |
| Payload size | Smaller | Larger |
| Human-readable | No | Yes |
| Parsing speed | Faster | Slower |
| Tooling | Compiler-generated code | Native in every language |
| Debugging | Needs a decoder | curl and read it |
Where protobuf shows up
Protobuf is the default wire format for gRPC, which pairs it with HTTP/2 for typed, streaming service-to-service calls. It’s common in internal microservice fleets where every service is written by people who control both ends of the wire and can regenerate code when a schema changes. It’s less common on public-facing REST APIs, where JSON’s readability and universal support in browsers win out — nobody wants to force every API consumer to install a protobuf compiler just to make a request.
Protobuf isn’t limited to RPC, either. It’s also used as a storage and interchange format on its own — anywhere you want a compact, versioned binary representation of structured data without hand-rolling a parser.
Schema evolution and versioning
Protobuf was designed to let schemas change without breaking old clients or servers. The rules that make this safe:
- Never reuse a field number. Once a number has shipped, it’s permanently tied to that field’s meaning, even if you later remove the field.
- New fields are safe to add. Old code simply ignores fields it doesn’t recognize; new code treats missing fields as unset (or their default value).
- Don’t change a field’s type once it’s shipped — that changes how the bytes are interpreted and breaks compatibility.
- Removing a field is safe as long as you don’t reuse its number later. Mark it
reservedin the.protofile to prevent that mistake.
This forward- and backward-compatibility is a big part of why protobuf is popular for long-lived internal APIs: services can be deployed independently and out of order without a synchronized rollout.
When JSON is still the better choice
Protobuf isn’t a universal upgrade. Reach for JSON instead when:
- The API is public and consumers you don’t control need to inspect payloads without tooling.
- Payloads are small and infrequent enough that size and parse speed don’t matter.
- You want to
curlan endpoint and read the response directly, which matters a lot during debugging and incident response. - Your stack is browser-heavy —
JSON.parseis built in everywhere; protobuf needs a JS library and generated code.
Plenty of systems end up using both: protobuf for internal service-to-service traffic where speed and schema discipline matter, JSON at the public edge where compatibility and readability matter more.
The takeaway
Protocol Buffers trade human readability for size and speed: a .proto schema compiles to generated code, messages are encoded as compact tagged binary instead of text, and disciplined field-numbering keeps old and new versions compatible. That makes it a strong default for internal RPC — especially with gRPC — and a poor fit for public APIs where anyone should be able to read a response without a decoder.
Tagged
Keep reading
Takina · · 4 min read Node.js Buffer Explained: Binary Data Handling
Node's Buffer class holds raw binary data outside the V8 heap, letting Node handle files, sockets, and streams efficiently. Here's how it works.
Takina · · 4 min read What Is tRPC? End-to-End Typesafe APIs Explained
tRPC lets TypeScript clients call server functions with full type inference and no schema or codegen step. How it works and where it fits.
Chisato · · 4 min read Token Bucket vs Leaky Bucket Rate Limiting
Token bucket allows bursts up to a cap; leaky bucket smooths traffic to a constant rate. How each rate-limiting algorithm works and when to pick it.