Serialization 10 📄 MessagePack — Binary JSON Without a Schema
MessagePack is a binary serialization format that preserves JSON’s data model while removing JSON’s textual overhead. It encodes the same types—maps, arrays, strings, numbers, booleans, null—but writes them as compact binary tags and length-prefixed payloads rather than quoted keys and decimal digits. The format is schema-less: the type information is embedded in the first byte of each value, so a decoder can read the data without a separate .proto file or schema registry. This is its defining trade-off against Protobuf: MessagePack is simpler to adopt, but it provides no evolution contract and no schema-enforced compatibility.
This chapter covers MessagePack’s format families, the schema-less trade-off, the performance characteristics measured against JSON, and the security considerations that arise when the format is used for untrusted input. The sections that follow walk through the binary encoding, the comparison with JSON and Protobuf, and the cases where MessagePack is the correct choice.
Key point: MessagePack is a compact, schema-less binary encoding of JSON’s data model; it is smaller and faster than JSON but provides no schema-enforced evolution guarantees, and its typeless deserialization paths have produced security vulnerabilities in library implementations.
a. The format families and binary encoding
MessagePack defines a set of format families, each identified by a leading byte or byte range. The specification is maintained in the msgpack/msgpack repository, and the format is implemented across dozens of languages .
The primitive types use fixed-length encodings. nil is a single byte 0xc0. Booleans are 0xc2 for false and 0xc3 for true. Integers use the smallest representation that fits: positive fixint stores values 0–127 in one byte, negative fixint stores −32 to −1 in one byte, and larger values use uint8 through uint64 or int8 through int64 with explicit type bytes . Floats use IEEE 754 single or double precision with a type byte.
Strings and binary data use length-prefixed formats. A string of up to 31 bytes uses fixstr with the length in the lower five bits of the first byte. Longer strings use str8, str16, or str32 with 1, 2, or 4 length bytes respectively. Binary data has a parallel bin family, separated from strings in the newer specification . Arrays and maps use fixarray/fixmap for small collections and array16/map16/array32/map32 for larger ones.
# A string "hello" in MessagePack
A5 68 65 6C 6C 6F
# 0xA5 = fixstr, length 5 (0xA5 & 0x1F = 5)
# 68 65 6C 6C 6F = "hello"
# An integer 150
CC 96
# 0xCC = uint8, 0x96 = 150
# A small integer 42 (positive fixint)
2A
# 0x2A = 42, encoded in one byte
The compactness comes from these type-specific encodings. A small integer that takes four or more bytes as a JSON string of digits takes one byte in MessagePack. A short string pays one length byte instead of two quotes and escape handling. The savings are largest for numeric-heavy data and smallest for long text where the quotes are a small fraction of the payload.
b. Schema-less trade-offs against JSON and Protobuf
MessagePack’s position is between JSON and Protobuf. Against JSON, it wins on size and speed. A study comparing the two in Go found that MessagePack reduced end-to-end latency for a 5,000-record dataset from 13.269 ms to 3.894 ms and produced a smaller payload (335.87 KB vs 350.53 KB); the advantage grew with dataset size, particularly in decoding and end-to-end latency . An IEEE study found that MessagePack incurred 144 bytes of first-call container allocation and then zero incremental per-message allocation, compared to JSON’s 528–1,104 bytes per-message allocation . A benchmark of JSON-compatible binary specifications found that schema-less formats including MessagePack can produce messages up to 63% smaller than JSON .
Against Protobuf, MessagePack loses the size competition on most data. The same benchmark found that schema-driven formats like Protobuf can produce bit-strings up to 95% smaller than JSON, and a specific Tier 1 document was 23 bytes in Protobuf versus 61 bytes in MessagePack . The exception is cases where MessagePack happens to be more space-efficient than Protobuf for specific data shapes, which the literature notes but does not generalize . The structural reason is that Protobuf omits field names entirely and uses field numbers in tags, while MessagePack encodes map keys as strings in the payload. For repeated records with the same keys, Protobuf’s advantage compounds.
The schema-less property is the reason to choose MessagePack over Protobuf. There is no .proto file to write, no code generation step, no schema registry to maintain. The encoder and decoder agree on the wire format through the specification, and the data types are embedded in the payload. For a system where the producers and consumers are controlled by the same team, or where the data shape is simple and stable, this simplicity is worth the size cost. For a system that needs strict evolution guarantees across independent teams, Protobuf’s schema is the safer choice.
c. Security considerations
MessagePack’s schema-less design has a security consequence: deserialization must decide what types to construct from the payload. Libraries that support “typeless” or “object” deserialization—where the payload can specify arbitrary types—expose an attack surface similar to YAML’s unsafe loaders.
MessagePack-CSharp, the .NET implementation, had multiple CVEs in 2026 related to this. CVE-2026-48517 affected the ThrowIfDeserializingTypeIsDisallowed safety check, which validated only the outer type name and did not recursively inspect array element types or generic type arguments. A blocked type wrapped inside List<T> or object[] passed the outer check and was materialized by the formatter machinery, enabling remote code execution in applications that deserialized untrusted input . CVE-2026-48109 affected the optional LZ4 decompression path, where a crafted payload could trigger out-of-bounds reads and process termination . CVE-2026-48515 affected multi-dimensional array formatters, which allocated arrays based on payload-declared dimensions before validating the element count, enabling memory exhaustion from a small payload . All three were fixed in versions 2.5.301 and 3.1.7.
The Microsoft documentation for SignalR’s MessagePack protocol recommends reviewing CVE-2020-5234 and applying MessagePackSecurity.UntrustedData when deserializing data from untrusted sources . The pattern is the same as YAML’s safe loader: restrict the types that can be constructed, and do not use the typeless path for input the application did not produce. A MessagePack decoder that constructs only known, registered types is safe; one that constructs arbitrary types from the payload is not.
Complete Example Session
# ============================================
# PART 1: THE SAME DATA IN JSON AND MESSAGEPACK
# ============================================
# JSON (48 bytes):
# {"id":42,"name":"widget","in_stock":true,"tags":["a","b"]}
# MessagePack (conceptual bytes):
# 84 # fixmap, 4 entries
# A2 69 64 # fixstr "id"
# 2A # positive fixint 42
# A4 6E 61 6D 65 # fixstr "name"
# A6 77 69 64 67 65 74 # fixstr "widget"
# A8 69 6E 5F 73 74 6F 63 6B # fixstr "in_stock"
# C3 # true
# A4 74 61 67 73 # fixstr "tags"
# 92 A1 61 A1 62 # fixarray 2, "a", "b"
# ============================================
# PART 2: THE SIZE AND SPEED COMPARISON
# ============================================
# Go study, 5000 records :
# JSON: 350.53 KB, 13.269 ms end-to-end
# MessagePack: 335.87 KB, 3.894 ms end-to-end
#
# IEEE study :
# JSON: 528-1,104 B per-message allocation
# MessagePack: 0 B incremental after first call
# ============================================
# PART 3: THE SCHEMA TRADE-OFF
# ============================================
# Protobuf for the same record: ~25 bytes (field numbers, no names)
# MessagePack: map keys are strings in the payload
# For repeated records, Protobuf's advantage grows
# ============================================
# PART 4: THE SECURITY RULE
# ============================================
# Safe: decode into a known, registered type
# Unsafe: typeless decode of untrusted input
# (CVE-2026-48517 in MessagePack-CSharp)
The parts covered the binary encoding, the measured size and speed comparison, the schema trade-off against Protobuf, and the security rule for untrusted input.
Quick Reference
Format Families
| Family | Example | Notes |
|---|---|---|
| Nil | 0xc0 | Single byte |
| Boolean | 0xc2/0xc3 | false/true |
| Positive fixint | 0x00–0x7f | 0–127, one byte |
| Negative fixint | 0xe0–0xff | −32 to −1, one byte |
| String | fixstr / str8 / str16 / str32 | Length-prefixed UTF-8 |
| Binary | bin8 / bin16 / bin32 | Length-prefixed bytes |
| Array | fixarray / array16 / array32 | Element count prefix |
| Map | fixmap / map16 / map32 | Key-value pair count |
Comparison
| Aspect | MessagePack | JSON | Protobuf |
|---|---|---|---|
| Schema | None | None | Required |
| Size vs JSON | ~4–63% smaller | Baseline | ~95% smaller |
| Decode speed vs JSON | Faster | Baseline | Faster |
| Evolution guarantee | None | None | Field numbers |
| Debuggability | Binary | Text | Binary + schema |
Security CVEs (MessagePack-CSharp)
| CVE | Issue | Fixed |
|---|---|---|
| CVE-2026-48517 | Typeless type check not recursive | 2.5.301, 3.1.7 |
| CVE-2026-48109 | LZ4 decompression OOB read | 2.5.301, 3.1.7 |
| CVE-2026-48515 | Multi-dim array allocation before validation | 2.5.301, 3.1.7 |
Best Practices
✅ Do This:
# Use MessagePack when JSON is too slow or too large
# and no schema contract is needed # ✅
# Decode into a known, registered type
# (avoid typeless deserialization of untrusted input) # ✅
# Apply MessagePackSecurity.UntrustedData in .NET
# when processing external data # ✅
# Measure against JSON before switching
# (payload size, encode/decode latency) # ✅
❌ Don’t Do This:
# Use MessagePack where Protobuf's evolution guarantees matter
# (independent teams, long-lived schemas) # ❌
# Typeless decode of untrusted input
# (CVE-2026-48517 pattern) # ❌
# Assume MessagePack is always smaller than Protobuf
# (field names in map keys add up) # ❌
# Use MessagePack for human-edited configuration
# (binary is unreadable without a parser) # ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| RCE from typeless decode | Payload specifies arbitrary types | Decode into registered types only |
| Memory exhaustion | Array dimensions trusted before count | Use patched library versions |
| No schema evolution | Schema-less format | Coordinate changes across teams |
| Debugging difficulty | Binary payload | Use a MessagePack decoder |
| Protobuf is smaller | Map keys in payload | Accept or switch to Protobuf |
Real-World Examples
1. Small integer
2A # 42 in one byte
2. String
A5 68 65 6C 6C 6F # "hello"
3. Map
84 A2 69 64 2A ... # 4-entry map
4. Array
92 A1 61 A1 62 # ["a", "b"]
5. SignalR MessagePack
AddMessagePackProtocol(); // server
6. Untrusted data setting
options.Security = MessagePackSecurity.UntrustedData;
7. Registered type decode
Deserialize<ProductCreated>(bytes) // concrete type
8. Go performance result
MessagePack: 335.87 KB, 3.894 ms
9. JSON performance result
JSON: 350.53 KB, 13.269 ms
10. Patched version
MessagePack-CSharp >= 2.5.301 / 3.1.7
Visual
MessagePack Encoding
┌─────────────────────────────────────────────────────────┐
│ "id": 42 │
│ │
│ A2 69 64 2A │
│ ▲ ▲ ▲ ▲ │
│ │ │ │ └── positive fixint 42 (1 byte) │
│ │ │ └────── "id" (2 bytes) │
│ │ └───────── fixstr, length 2 │
│ └──────────── type byte │
│ │
│ JSON: "id": 42 → 9 bytes │
│ MessagePack: → 5 bytes │
└─────────────────────────────────────────────────────────┘
Position in the Format Landscape
┌─────────────────────────────────────────────────────────┐
│ SCHEMA-LESS SCHEMA-BASED │
│ │
│ JSON MessagePack Protobuf Avro │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ Text Binary Binary Binary │
│ Large Compact Compact Compact │
│ Slow Fast Fast Fast │
│ No No Field Schema │
│ contract contract numbers resolution │
│ │
│ MessagePack: JSON's model, binary encoding, no schema │
└─────────────────────────────────────────────────────────┘
The Security Rule
┌─────────────────────────────────────────────────────────┐
│ MESSAGEPACK DESERIALIZATION │
│ │
│ Payload → Decoder → ? │
│ │
│ Registered type: │
│ Decoder knows the target type, constructs it safely │
│ │
│ Typeless: │
│ Payload specifies the type, decoder constructs it │
│ → CVE-2026-48517: blocked type wrapped in List<T> │
│ → RCE in unpatched MessagePack-CSharp │
│ │
│ Rule: do not typeless-decode untrusted input │
└─────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Type | Schema-less binary serialization |
| Data model | JSON-compatible (maps, arrays, scalars) |
| Size vs JSON | Up to 63% smaller |
| Size vs Protobuf | Usually larger |
| Speed vs JSON | Faster (4.1ms vs 13.3ms in one study) |
| Evolution guarantee | None |
| Security risk | Typeless deserialization (CVEs) |
| Safe pattern | Decode into known, registered types |
Key takeaways:
- MessagePack is JSON’s data model in binary. It encodes the same maps, arrays, strings, numbers, and booleans, but uses type bytes and length prefixes instead of quotes and digits. The result is smaller and faster, with no schema to write or maintain .
- It sits between JSON and Protobuf. It beats JSON on size and speed, but loses to Protobuf on size because map keys are strings in the payload rather than field numbers in a schema .
- Schema-less is the reason to choose it and the reason to avoid it. No
.proto, no codegen, no registry—but also no evolution contract. For controlled producers and consumers, the simplicity is worth it; for independent teams with long-lived schemas, Protobuf is safer. - Typeless deserialization is the security hazard. MessagePack-CSharp’s 2026 CVEs all stemmed from the typeless path constructing types from the payload. Decode into known, registered types, and apply
MessagePackSecurity.UntrustedDatawhen the input is external . - The performance advantage grows with volume. The Go study found the gap widened with dataset size, particularly in decoding and end-to-end latency . At low volumes, JSON’s debuggability is worth more than MessagePack’s speed.
Remember: MessagePack is the format for when JSON is too slow or too large but a schema is too much ceremony. It keeps JSON’s flexibility and removes its text overhead. The cost is that the payload carries its own type information, which means a decoder that trusts the payload can be made to construct things the application did not intend. Use it with registered types, measure the gain against JSON, and do not expect it to replace Protobuf where evolution guarantees matter.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!