MQTT Fundamentals

MQTT 3.1.1 vs 5.0: What Changed and Should You Upgrade?

A practical MQTT 3.1.1 vs 5.0 comparison covering protocol behavior changes, client library support and a step-by-step migration checklist for upgrading a mixed fleet gradually.

Two versions of an MQTT packet side by side with new property fields highlighted, illustrating MQTT 3.1.1 vs 5.0
Run this on your own MQTT brokerGet a private, managed MQTT broker on the Free plan and start from ready-made code samples.
Try it free Code samples No card required

MQTT 3.1.1 vs 5.0 is the version question every team faces once a project grows beyond a prototype: is the newer protocol worth the migration, and can old and new devices coexist? This guide compares how the two versions behave on the wire and in practice, lists which popular client libraries support MQTT 5, and gives you a migration checklist for upgrading a mixed fleet one group of clients at a time.

A Quick History

MQTT 3.1.1 became an OASIS standard in 2014 and is still the most widely deployed version. It is small, stable and supported by practically every client library and device. MQTT 5.0 followed as an OASIS standard in 2019. It kept the same core model, topics, QoS levels, retained messages and Last Will, and added the features large deployments had been building by hand: clearer errors, expiry, flow control, metadata and load balancing. There was no version 4; the protocol level byte for 3.1.1 is 4 and for 5.0 is 5, which is why libraries often call 3.1.1 "protocol version 4".

The good news for migration is that a broker can serve both versions at once. Each client declares its version in the CONNECT packet, and messages flow between 3.1.1 and 5.0 clients on the same topics.

MQTT 3.1.1 vs 5.0 Side by Side

Behavior MQTT 3.1.1 MQTT 5.0
Error reporting CONNACK return codes 0 to 5; most other failures just close the connection Reason codes on CONNACK, PUBACK, SUBACK, UNSUBACK, DISCONNECT and more, plus optional reason strings
Session lifetime Clean session flag: forever or never Clean start plus session expiry interval in seconds
Message lifetime Messages wait indefinitely in sessions Message expiry interval per message
Load-balanced consumers Not standard Shared subscriptions with $share/group/filter
Topic overhead Full topic in every PUBLISH Topic alias replaces topic with an integer
Metadata Only in the payload User properties, content type, payload format indicator
Request/response Convention only Response topic and correlation data
Flow control None standard Receive maximum, maximum packet size
Server disconnect Server closes the socket silently Server sends DISCONNECT with a reason code
Subscription options QoS only No local, retain as published, retain handling, subscription identifiers
Will message Sent immediately on ungraceful disconnect Optional will delay interval
Authentication Username and password Plus enhanced authentication via AUTH packet
Server capabilities Implicit Server can announce maximum QoS, retain available, keep alive override, assigned client ID

The Changes That Matter Most in Practice

Reason codes. In 3.1.1, if a broker rejects a publish because of a quota or permission, the client often just sees the connection drop and has to guess. In MQTT 5 the broker can say why, for example 0x97 (quota exceeded) or 0x87 (not authorized). That alone shortens debugging dramatically. The MQTT reason codes reference lists them all.

Session and message expiry. With 3.1.1 a persistent session lives until the client returns, which can mean a broker holding messages for devices that were decommissioned months ago. MQTT 5 lets the client say "keep my session for 10 minutes", and lets publishers say "this command is useless after 30 seconds". See MQTT session expiry interval for tuning advice.

Shared subscriptions. Several instances of a backend service can subscribe to $share/workers/orders/+/new and the broker distributes messages among them. Before MQTT 5 this required broker-specific extensions or a separate queue.

User properties and request/response. Metadata such as a schema version or trace ID can travel in properties instead of being wrapped into every payload, and request/response flows get a standard response topic and correlation data.

For a feature-by-feature tour with examples, read MQTT 5 features.

What Does Not Change

Topic syntax and wildcards, the three QoS levels, retained messages, Last Will, keep alive, ports and TLS all work the same way. Payloads are still opaque bytes. A 3.1.1 subscriber receives messages published by a 5.0 client; it simply does not see the MQTT 5 properties. That compatibility is what makes a gradual upgrade possible.

Client Library Support

Platform Library MQTT 5 support
Python paho-mqtt 2.x Yes, pass protocol=mqtt.MQTTv5
Node.js and browsers MQTT.js v5 Yes, set protocolVersion: 5 (default is 4)
Go Eclipse Paho paho.mqtt.golang No, 3.1.1 only
Go Eclipse Paho paho.golang Yes, MQTT 5 only
Java Eclipse Paho Java (mqttv5 package), HiveMQ MQTT Client Yes
.NET MQTTnet Yes
ESP32 (ESP-IDF) esp-mqtt Yes, when MQTT 5 support is enabled in the build configuration
Arduino PubSubClient, AsyncMqttClient No, 3.1.1 only
CLI mosquitto_pub and mosquitto_sub Yes, with -V mqttv5

Library versions move, so confirm against the official documentation before you commit. The pattern to notice: server-side and gateway libraries generally support MQTT 5, while many popular microcontroller libraries are still 3.1.1 only. That is the main reason fleets end up mixed.

Using MQTT 5 Features in Code

This Python example connects with MQTT 5, asks for a 10-minute session, publishes a command with a 30-second expiry and user properties, and prints reason codes. It runs against the public MQTT test broker, which supports both versions and session expiry up to 30 minutes.

import time
import uuid

import paho.mqtt.client as mqtt
from paho.mqtt.packettypes import PacketTypes
from paho.mqtt.properties import Properties

PREFIX = f"connectmq/demo/{uuid.uuid4().hex[:8]}"

def on_connect(client, userdata, flags, reason_code, properties):
    print("CONNACK:", reason_code, "| session present:", flags.session_present)
    client.subscribe(f"{PREFIX}/cmd/#", qos=1)

def on_subscribe(client, userdata, mid, reason_code_list, properties):
    print("SUBACK:", [str(rc) for rc in reason_code_list])

def on_message(client, userdata, msg):
    props = msg.properties
    user_props = getattr(props, "UserProperty", None)
    expiry = getattr(props, "MessageExpiryInterval", None)
    print(f"{msg.topic} {msg.payload.decode()} user={user_props} expiry={expiry}")

def on_publish(client, userdata, mid, reason_code, properties):
    print("PUBACK:", reason_code)

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
                     client_id=f"v5-demo-{uuid.uuid4().hex[:8]}",
                     protocol=mqtt.MQTTv5)
client.on_connect = on_connect
client.on_subscribe = on_subscribe
client.on_message = on_message
client.on_publish = on_publish

connect_props = Properties(PacketTypes.CONNECT)
connect_props.SessionExpiryInterval = 600  # keep session 10 minutes

client.connect("broker.connectmq.com", 1883, keepalive=30,
               clean_start=False, properties=connect_props)
client.loop_start()
time.sleep(1)

pub_props = Properties(PacketTypes.PUBLISH)
pub_props.MessageExpiryInterval = 30
pub_props.UserProperty = [("schema", "v2"), ("source", "ops-console")]
pub_props.ContentType = "application/json"

client.publish(f"{PREFIX}/cmd/pump7", '{"action":"stop"}', qos=1,
               properties=pub_props)
time.sleep(2)
client.loop_stop()
client.disconnect()

To see interoperability, run a 3.1.1 subscriber on the same topic in another terminal. It receives the payload but not the properties.

mosquitto_sub -h broker.connectmq.com -p 1883 -V mqttv311 \
  -i v311-check-$RANDOM -t "connectmq/demo/+/cmd/#" -v

Migration Checklist

Use this checklist to move from 3.1.1 to 5.0 without a big-bang cutover.

  1. Confirm broker support. Your broker must accept both versions simultaneously. ConnectMQ does, so old and new clients can share topics during the transition.
  2. Inventory clients by library. List every device type, gateway and service with its MQTT library and version. Mark which can move to MQTT 5 today.
  3. Upgrade backend services first. They gain the most (reason codes, shared subscriptions, flow control) and are the easiest to redeploy.
  4. Replace clean session logic. Map clean_session=True to clean start with session expiry 0, and clean_session=False to clean start false with an explicit expiry instead of "forever".
  5. Handle reason codes. Log reason codes and reason strings on CONNACK, SUBACK, PUBACK and DISCONNECT, and stop retrying on non-retryable codes such as "not authorized".
  6. Do not depend on properties for critical data yet. While 3.1.1 subscribers remain, anything they need must stay in the payload or the topic.
  7. Introduce expiry deliberately. Add message expiry to commands and time-sensitive alerts first, where stale delivery is harmful.
  8. Adopt shared subscriptions for horizontally scaled consumers, and remove any homegrown deduplication between instances.
  9. Upgrade gateways and devices last, by group, watching connection state and error rates after each wave.
  10. Retest the basics. Reconnect, Last Will, retained state and persistent sessions should behave as before; the MQTT testing checklist covers each.

Should You Upgrade?

For new projects, start with MQTT 5 wherever your libraries support it. For existing fleets, upgrade services and gateways now and devices opportunistically, for example at their next firmware cycle. There is little reason to rush constrained devices whose library is 3.1.1 only, because a broker that speaks both versions removes the pressure. The MQTT tutorial covers the shared fundamentals if you are onboarding new team members.

A managed MQTT broker that supports both versions makes the transition uneventful. ConnectMQ accepts MQTT 3.1.1 and 5.0 clients on the same broker and supports MQTT 5 features including session expiry, message expiry, topic alias, response topic, retained messages and reason codes, with shared subscriptions on the paid Serverless plan.

Upgrade on ConnectMQ at Your Own Pace

Create a free Serverless broker at app.connectmq.com, no credit card required, connect one 3.1.1 device and one MQTT 5 service, and confirm that they interoperate before you plan the rest of the migration.

Frequently Asked Questions

Are MQTT 3.1.1 and MQTT 5.0 compatible?

On the same broker, yes. Clients choose their version at connect time, and messages flow between versions. A 3.1.1 subscriber simply does not receive MQTT 5 properties.

What is the biggest benefit of MQTT 5?

For most teams it is reason codes, because failures stop being silent disconnects. Session and message expiry and shared subscriptions are close behind.

Why is MQTT 3.1.1 called protocol version 4?

The CONNECT packet carries a protocol level byte, which is 4 for MQTT 3.1.1 and 5 for MQTT 5.0. Libraries such as MQTT.js use that number in their options.

Do I need to upgrade my ESP32 devices to MQTT 5?

Not necessarily. If you use a 3.1.1 library like PubSubClient, the devices can keep working against a broker that supports both versions while your backend adopts MQTT 5.

Related articles

Why Your MQTT Client Keeps Disconnecting (and How to Fix It)

Reconnect loops are one of the most common MQTT problems. Learn the five usual causes, from duplicate client IDs to keep alive timeouts and oversized packets, and follow a diagnosis flow that pinpoints yours.

8 min read

Who Invented MQTT? The History from Oil Pipelines to IoT

MQTT was invented in 1999 by Andy Stanford-Clark of IBM and Arlen Nipper of Arcom to monitor oil pipelines over satellite. Follow its path to open source, OASIS, ISO and today's managed cloud brokers.

7 min read

MQTT Maximum Message Size: Limits and Large Payload Strategies

MQTT can technically carry a 256 MB packet, but brokers, client libraries and devices impose far lower limits. Learn where the real limits are, what happens when you exceed them, and how to send large payloads.

7 min read

All articles →

Your private MQTT broker is a few clicks away

Create an account, get your broker credentials and connect your first device — no servers to install, patch or scale.