Choosing an MQTT payload format is one of the first design decisions in any IoT project, and one of the hardest to change later. MQTT treats the payload as opaque bytes, so JSON, Protobuf, CBOR or a custom binary layout all work on the wire. This comparison shows how the three most common encodings differ in size, schema management and tooling, using byte counts for the same sensor message, so you can pick the right one for your devices.
MQTT Is Payload-Agnostic
An MQTT PUBLISH packet contains a topic, some flags, optional MQTT 5 properties and a payload of up to 256 MB by protocol limit (brokers enforce much lower limits in practice). The MQTT broker never looks inside the payload. That gives you freedom, but it also means nothing stops two teams from publishing incompatible formats to the same topic.
MQTT 5 adds two properties that help:
- Payload Format Indicator: 0 means unspecified bytes, 1 means UTF-8 text.
- Content Type: a free-form string, typically a MIME type such as
application/jsonorapplication/x-protobuf.
Setting them costs a few bytes and lets generic tools, loggers and consumers handle your messages correctly.
The Sample Message
To compare fairly we encode the same pump reading in each format:
{"deviceId":"pump-07","ts":1760600000,"temp":21.5,"pressure":2.31,"rpm":1480,"running":true}
Six fields: a string ID, a Unix timestamp, two decimal measurements, an integer and a boolean. That is a realistic shape for industrial and building telemetry.
Size Comparison
The byte counts below are calculated from the encoding rules of each format for this exact message:
| Encoding | Bytes | Notes |
|---|---|---|
| JSON, pretty-printed (2-space indent) | 117 | Human-friendly, wasteful on the wire |
| JSON, compact | 92 | The common default |
| JSON, compact with short keys | 65 | {"d":"pump-07","t":1760600000,"tp":21.5,"p":2.31,"r":1480,"on":1} |
| CBOR, floats as 64-bit | 74 | Same keys as JSON, binary values |
| CBOR, floats as 32-bit | 64 | Precision trade-off on pressure |
Protobuf, floats as float |
30 | Field numbers instead of names |
A few observations:
- Most of JSON's size is the key names and number text. Shortening keys gets you a long way before you change formats at all.
- CBOR keeps the self-describing map structure, so key names still travel with every message. Its savings come from binary numbers and booleans.
- Protobuf is small because the field names live in a schema file, not in the message. The receiver must have that schema.
Remember the MQTT header and topic on top of the payload. A topic like site/plant1/line1/pump-07/telemetry is 35 bytes, which is larger than the entire Protobuf payload. If bandwidth really matters, look at topic length and MQTT 5 topic aliases too, not just encoding. For a broader view of where bytes go, see MQTT bandwidth usage.
JSON: The Default for Good Reasons
JSON wins on everything except size:
- Readable in any MQTT client, log file or terminal.
- Every language and platform has a parser, including ArduinoJson on microcontrollers.
- Node-RED, Telegraf, Home Assistant and most dashboards accept it without configuration.
- Adding a field rarely breaks older consumers.
Its weaknesses: no schema enforcement, ambiguous number types (is 1 a boolean, an integer or a float?), and parsing cost on very small microcontrollers. For most projects with mains-powered devices and Wi-Fi or Ethernet, those trade-offs are acceptable.
Protobuf: Smallest, with a Schema
Protocol Buffers encode each field as a field number plus a compact value. The schema for our message:
syntax = "proto3";
message PumpReading {
string device_id = 1;
uint64 ts = 2;
float temp = 3;
float pressure = 4;
uint32 rpm = 5;
bool running = 6;
}
Strengths: smallest payloads, strong typing, generated code in many languages, and well-defined rules for evolving schemas (add fields with new numbers, never reuse old ones). Sparkplug B, widely used in industrial MQTT, is built on Protobuf for exactly these reasons; see MQTT Sparkplug B and the Unified Namespace.
Weaknesses: unreadable without the schema, so debugging needs decoding tools; schema files must be distributed and versioned across every producer and consumer; and generic integrations cannot use the data without a decode step.
CBOR: Binary JSON
CBOR (RFC 8949) keeps JSON's data model, maps, arrays, strings and numbers, but encodes it in binary. It needs no schema, so any CBOR decoder can turn a message back into a JSON-like structure.
Strengths: smaller than JSON, self-describing, native binary byte strings (no base64), and popular in constrained protocols such as CoAP. Weaknesses: savings are moderate unless you also shorten keys, tool support is thinner than for JSON, and float encoding differs between libraries, which can surprise you when you compare bytes.
Decision Checklist
- Are humans going to read these messages often? Prefer JSON.
- Are devices on metered cellular, LoRa backhaul or satellite? Consider Protobuf, or compact JSON with short keys as a first step.
- Many teams and languages consuming the same data? Protobuf's schemas pay for themselves; otherwise JSON with a documented schema.
- Need binary blobs inside structured data? CBOR handles them natively.
- Integrating with off-the-shelf tools? JSON, or plan a decode step that republishes JSON.
- Industrial interoperability? Look at Sparkplug B before inventing your own Protobuf schema.
Whatever you choose, version it. A schema field in the payload, a version in the topic, or an MQTT 5 user property all work; the important thing is that consumers can tell formats apart.
Publishing with Content Type in Python
This paho-mqtt 2.x example publishes the compact JSON reading with the MQTT 5 payload format indicator and content type set, to the public MQTT test broker:
import json
import uuid
import paho.mqtt.client as mqtt
from paho.mqtt.properties import Properties
from paho.mqtt.packettypes import PacketTypes
reading = {"deviceId": "pump-07", "ts": 1760600000, "temp": 21.5,
"pressure": 2.31, "rpm": 1480, "running": True}
payload = json.dumps(reading, separators=(",", ":")).encode()
print("payload bytes:", len(payload)) # 92
props = Properties(PacketTypes.PUBLISH)
props.PayloadFormatIndicator = 1
props.ContentType = "application/json"
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
client_id=f"fmt-{uuid.uuid4().hex[:8]}",
protocol=mqtt.MQTTv5)
client.connect("broker.connectmq.com", 1883, keepalive=30)
client.loop_start()
client.publish("connectmq/demo/f6n1/pump-07/telemetry", payload,
qos=1, properties=props).wait_for_publish()
client.loop_stop()
client.disconnect()
Note separators=(",", ":"): Python's default json.dumps adds a space after every comma and colon, which would make the payload larger than the compact figure in the table.
Payload Pitfalls
- Mixing formats on one topic. Consumers break in confusing ways. One topic, one format.
- Floating-point surprises. 32-bit floats cannot represent 2.31 exactly. Decide on precision deliberately or send scaled integers.
- Timestamps in local time. Use UTC Unix time or ISO 8601 with an offset.
- Exceeding size limits. Batching many readings into one message can hit the broker's maximum message size; see the MQTT message size limit.
Payload Formats on ConnectMQ
ConnectMQ carries any payload format, since MQTT payloads are opaque bytes. On top of that, ConnectMQ can decode Sparkplug B payloads (Protobuf or JSON) and republish them into readable ISA-95 topics. Message limits are 256 KB on the free Serverless plan and 1 MB on the paid Serverless plan, which is ample for any single-reading encoding above. If you would rather not operate the infrastructure, a managed MQTT broker lets you focus on the payload contract. The MQTT tutorial covers the rest of the protocol.
Get Started with ConnectMQ
Create a free broker at app.connectmq.com on the Serverless plan, no credit card required. Publish the same reading in JSON and in your binary format of choice, and compare them side by side in the in-browser client of the ConnectMQ portal.
Frequently Asked Questions
What is the best MQTT payload format?
There is no single best. JSON is the safest default for interoperability and debugging; Protobuf is best when bandwidth is tight and schemas are managed well.
Does the broker validate the payload format?
No. MQTT brokers treat payloads as opaque bytes. Validation happens in your consumers.
Should I compress JSON payloads?
For single small readings, compression often saves little and costs CPU. It helps more for large batched messages.
How do I tell consumers which format a message uses?
In MQTT 5, set the Content Type property. In MQTT 3.1.1, use a topic convention or a version field inside the payload.



