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 JSONParse speedSchemaHuman-readable
JSON1.0×1.0×noneyes
Protobuf0.4–0.6×5–10×requiredno
MessagePack0.6–0.8×2–3×noneno
CBOR0.6–0.8×2–3×noneno
FlatBuffers1.0× (similar)zero-copyrequiredno
Cap’n Protosimilar to FBzero-copyrequiredno
Avro0.4–0.6×3–5×requiredno

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)

Pick by axis

AxisPick
Public APIJSON
Internal service-to-serviceProtobuf
Streaming pipelines (Kafka)Avro or Protobuf
Drop-in JSON replacementMessagePack
IoT / constrained devicesCBOR
Zero-copy neededFlatBuffers / Cap’n Proto
Logs / config / debugJSON

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

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 .