MQTT retained messages solve a simple but annoying problem: a client that subscribes to a topic has to wait for the next publish before it knows anything. With the retain flag, the broker keeps the last message on that topic and delivers it to every new subscriber immediately. In this guide you will see exactly how retained messages behave, try them on a public broker, and learn when to use them and when to avoid them.
What MQTT Retained Messages Are
Every MQTT PUBLISH packet carries a one-bit retain flag. When a client publishes with retain set to true, the broker does two things: it forwards the message to current subscribers as usual, and it stores a copy as the retained message for that exact topic. Each topic holds at most one retained message, so a new retained publish replaces the old one.
When a client later subscribes to a filter that matches that topic, the broker sends the stored message right after the SUBACK. The subscriber sees the retain flag set on that delivery, which tells it "this is the last known value, not a fresh event". Wildcard subscriptions work too: subscribing to factory/+/status returns the retained message of every matching topic at once.
A few rules that are easy to miss:
- Retention is per topic, not per client. Any client may overwrite the retained value.
- Publishing with retain and an empty (zero-byte) payload deletes the retained message.
- Messages forwarded to already-connected subscribers normally arrive with the retain flag cleared in MQTT 3.1.1. MQTT 5 adds the "retain as published" subscription option to keep it.
- The QoS of the retained delivery is the lower of the stored message QoS and the subscription QoS.
If you need a refresher on how a broker routes messages in general, read what an MQTT broker does first.
How the Retain Flag Changes Delivery
The easiest way to understand the difference is to compare the same publish with and without retain.
| Scenario | Retain false | Retain true |
|---|---|---|
| Subscriber online at publish time | Receives message | Receives message |
| Subscriber connects 5 minutes later | Receives nothing until next publish | Receives last value immediately |
| Second publish on same topic | Delivered, nothing stored | Delivered, replaces stored value |
| Publish empty payload | Delivers empty message | Deletes stored value |
| Wildcard subscription | Only future messages | Last value of every matching topic |
This is why retained messages are often described as "last known good value". They turn a stream of events into something you can query by subscribing.
Try Retained Messages on a Public Broker
You can test this in under a minute with the Mosquitto command-line tools against the public MQTT test broker at broker.connectmq.com. It accepts anonymous connections with any non-empty client ID. Use a unique topic prefix, because everything in the public area is readable by everyone.
First publish a retained value, then disconnect:
mosquitto_pub -h broker.connectmq.com -p 1883 -i demo-pub-r7k2 \
-t connectmq/demo/r7k2/thermostat/setpoint \
-m '{"setpoint":21.5,"unit":"C"}' -q 1 -r
Now subscribe from another terminal. Even though nobody is publishing, the value arrives instantly. The -v flag prints the topic and -F lets us show the retain flag explicitly:
mosquitto_sub -h broker.connectmq.com -p 1883 -i demo-sub-r7k2 \
-t 'connectmq/demo/r7k2/#' -F 'retained=%r %t %p'
You should see retained=1 followed by the topic and payload. Publish again without -r while the subscriber is open and the new line shows retained=0, because it is a live message rather than a stored one.
To delete the retained value, publish an empty retained message. The -n flag sends a null payload:
mosquitto_pub -h broker.connectmq.com -p 1883 -i demo-pub-r7k2 \
-t connectmq/demo/r7k2/thermostat/setpoint -r -n
Subscribe again and nothing comes back. We cover cleanup in more depth, including wildcard cleanup scripts, in how to delete a retained message.
Publishing Retained Messages from Python
In application code the retain flag is just a parameter. Here is a complete paho-mqtt 2.x example that publishes a retained device state and then reads it back with a fresh client:
import json
import time
import uuid
import paho.mqtt.client as mqtt
BROKER = "broker.connectmq.com"
PREFIX = f"connectmq/demo/{uuid.uuid4().hex[:6]}"
TOPIC = f"{PREFIX}/pump-07/state"
# 1. Publish a retained state and disconnect
pub = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"pub-{uuid.uuid4().hex[:8]}")
pub.connect(BROKER, 1883, keepalive=30)
pub.loop_start()
info = pub.publish(TOPIC, json.dumps({"running": True, "rpm": 1480}), qos=1, retain=True)
info.wait_for_publish()
pub.loop_stop()
pub.disconnect()
# 2. A new subscriber gets the stored value immediately
def on_connect(client, userdata, flags, reason_code, properties):
client.subscribe(TOPIC, qos=1)
def on_message(client, userdata, msg):
print(f"{msg.topic} retain={msg.retain} payload={msg.payload.decode()}")
sub = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"sub-{uuid.uuid4().hex[:8]}")
sub.on_connect = on_connect
sub.on_message = on_message
sub.connect(BROKER, 1883, keepalive=30)
sub.loop_start()
time.sleep(3)
sub.loop_stop()
sub.disconnect()
The subscriber prints retain=True even though it connected after the publisher had gone. In a dashboard or a digital twin, that means the UI can render the current state on load instead of showing blanks.
When to Use MQTT Retained Messages
Retained messages work best for data that describes a current state rather than an event:
- Device status. Online/offline flags, firmware version, operating mode. Combined with Last Will, this gives you reliable presence; see MQTT device online status.
- Configuration and setpoints. A device that reboots subscribes to its config topic and receives its settings without a separate API call.
- Slow-changing measurements. A tank level that updates every 10 minutes is useless to a dashboard that just opened unless the last value is retained.
- Discovery metadata. Many home and industrial tools publish retained descriptors so new consumers can find devices.
- Digital twin initial state. A twin needs a complete picture on startup, and retained topics provide it with a single wildcard subscribe.
When Not to Use Retained Messages
The retain flag is not a database and not a queue. Avoid it for:
- Events and commands. A retained "open valve" command will be replayed to every device that subscribes later, including after a reboot. That is a classic and dangerous bug. Commands should be non-retained, ideally with an expiry; see the MQTT message expiry interval.
- High-frequency telemetry history. Only the last message is kept. If you need history, run a subscriber that writes to a time-series database.
- Alarms that must be acknowledged. A retained alarm stays visible after it has been cleared unless something explicitly overwrites it.
Pitfalls and Troubleshooting
Stale values after a device is decommissioned. Retained messages live until someone deletes them. Add cleanup to your decommissioning procedure and publish an empty retained payload for every topic the device owned.
Ghost data from test runs. During development it is easy to leave retained junk on topics. A unique per-project prefix makes cleanup with a wildcard subscriber far easier.
Unexpected replays when re-subscribing. Every SUBSCRIBE triggers retained delivery again, which can surprise code that re-subscribes on reconnect. MQTT 5 offers the retain handling subscription option: 0 sends retained messages on every subscribe, 1 only if the subscription is new, 2 never. Check whether your client library exposes it.
Retain flag looks false on live messages. That is expected in MQTT 3.1.1. If your logic depends on knowing whether a message was originally retained, use MQTT 5 with retain as published.
Retained values after a broker restart. Whether retained messages survive a broker restart depends on the broker's persistence configuration. Design devices to republish their state periodically or on reconnect so a fresh broker is repopulated quickly, rather than assuming the retained store is permanent.
The behaviour is defined in the OASIS MQTT 5.0 specification if you want the exact normative wording.
Retained Messages on a Managed Broker
On a fully managed MQTT service you do not configure retained storage yourself, but you still own the topic design. ConnectMQ supports retained messages on MQTT 3.1.1 and 5.0, and its portal includes a live topic explorer and an in-browser MQTT client, so you can subscribe to your prefix and watch retained values arrive the moment you connect. That makes it easy to spot stale test data or a device that never cleaned up after itself.
A practical checklist before going to production:
- Decide per topic: state (retain) or event (no retain).
- Never retain commands.
- Republish state on every reconnect, not just on change.
- Clear retained topics when devices are retired.
- Keep retained payloads small; every new subscriber receives all of them at once.
For more on the protocol as a whole, the MQTT tutorial walks through publish, subscribe, QoS and sessions end to end, and the cloud MQTT broker guide explains how hosted brokers fit into a real deployment.
Get Started with ConnectMQ
Create a free broker at app.connectmq.com on the Serverless plan with no credit card required. You get a username, password and client ID in minutes, and you can watch your retained topics appear in the ConnectMQ topic explorer as soon as your first device publishes.
Frequently Asked Questions
How many retained messages can a topic have?
Exactly one. A new retained publish on the same topic replaces the previous one, and an empty retained payload removes it.
Do retained messages expire?
Not by default in MQTT 3.1.1. In MQTT 5 you can set a message expiry interval on a retained publish, after which the broker stops delivering it to new subscribers.
Does QoS affect retained messages?
The retained message keeps the QoS it was published with, and a new subscriber receives it at the lower of that QoS and its own subscription QoS.
Can I see which topics have retained messages?
Subscribe to a broad wildcard such as your/prefix/# and every retained message under it is delivered immediately. On ConnectMQ you can do the same from the in-browser client in the portal.



