JSON is universal but not optimal for everything. Binary formats save bandwidth, parse faster, encode types better. This post is when each fits.
The contenders
| Wire size vs JSON | Parse speed | Schema | Human-readable | |
|---|---|---|---|---|
| JSON | 1.0× | 1.0× | none | yes |
| Protobuf | 0.4–0.6× | 5–10× | required | no |
| MessagePack | 0.6–0.8× | 2–3× | none | no |
| CBOR | 0.6–0.8× | 2–3× | none | no |
| FlatBuffers | 1.0× (similar) | zero-copy | required | no |
| Cap’n Proto | similar to FB | zero-copy | required | no |
| Avro | 0.4–0.6× | 3–5× | required | no |
When each wins
Protobuf
- Service-to-service (gRPC ).
- Wire format for typed contracts.
- Code generation across languages.
- Schema evolution (additive).
message User {
uint64 id = 1;
string email = 2;
string full_name = 3;
}
MessagePack
- JSON drop-in for size + speed.
- No schema management.
- Wide language support.
- Used in MongoDB, Pinpoint, etc.
import msgpack
packed = msgpack.packb({"id": 1, "email": "[email protected]"}) # bytes
unpacked = msgpack.unpackb(packed) # dict
CBOR
- Internet-standard (RFC 8949).
- Similar perf to MessagePack.
- Better for IoT (deterministic encoding option).
- Used by COSE, WebAuthn.
FlatBuffers / Cap’n Proto
- Zero-copy access.
- Reads memory-mapped without parsing.
- For very high-throughput, latency-sensitive systems (game state, RTB).
- Ergonomics rougher than Protobuf.
Avro
- Schema in the data; great for streaming pipelines (Kafka).
- Schema evolution well-defined.
- Used heavily in data engineering.
JSON (still relevant)
- Public APIs (API Design Patterns ).
- Logs and config.
- Debugging.
- Anywhere humans look at the data.
Pick by axis
| Axis | Pick |
|---|---|
| Public API | JSON |
| Internal service-to-service | Protobuf |
| Streaming pipelines (Kafka) | Avro or Protobuf |
| Drop-in JSON replacement | MessagePack |
| IoT / constrained devices | CBOR |
| Zero-copy needed | FlatBuffers / Cap’n Proto |
| Logs / config / debug | JSON |
Schema evolution
The hard part of binary formats:
Protobuf rules
- Don’t reuse field numbers.
- Mark removed fields
reserved. - Adding optional fields is safe.
- Changing types is rarely safe.
message User {
uint64 id = 1;
reserved 2; // was 'name'; removed
string full_name = 3;
}
Avro
Schema is part of the data; readers can use a different schema than writers (with explicit compatibility rules). More flexible than Protobuf for ETL pipelines where schemas evolve and historic data must remain readable.
MessagePack / CBOR
No schema. Ad-hoc evolution. Can be a feature (flexibility) or bug (no checks).
Cost in real numbers
For a typical service-to-service call carrying a 1KB JSON payload:
- JSON: 1024 bytes wire, 50μs to encode/decode.
- Protobuf: 400 bytes wire, 5μs.
- MessagePack: 700 bytes wire, 15μs.
For 1B calls/day:
- JSON: ~1 PB egress.
- Protobuf: ~400 TB.
- 600 TB saved × $0.02/GB egress = $12k/day = $4.4M/year.
The numbers are why high-traffic services move to Protobuf.
What I’d ship today
- JSON for public APIs, configs, logs.
- Protobuf with gRPC for service-to-service.
- MessagePack in Redis cache values where size matters.
- Avro in Kafka pipelines for evolution discipline.
Read this next
- Go + gRPC + Protocol Buffers
- API Design Patterns 2026
- Kafka vs NATS vs RabbitMQ in 2026
- GraphQL vs tRPC vs gRPC vs REST
If you want my Protobuf + buf monorepo template, it’s at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .