Overview
Retained messages are a powerful MQTT feature that allows the broker to store the last message published to a topic and automatically deliver it to new subscribers. This ensures subscribers immediately receive the latest value without waiting for the next publish.
Only one retained message is stored per topic. Publishing a new retained message overwrites the previous one.
How Retained Messages Work
Key Characteristics
Persistence : Retained messages persist on the broker until replaced or cleared
Immediate delivery : New subscribers receive retained messages instantly
One per topic : Each topic stores at most one retained message
Flag indication : Subscribers can detect retained messages via the retain flag
QoS preserved : Retained messages maintain their QoS level
Publishing Retained Messages
Python Examples
Basic Retained Message
Device Status Monitor
Configuration Publisher
import paho.mqtt.client as mqtt
client = mqtt.Client( "retained_publisher" )
client.connect( "localhost" , 1883 )
# Publish with retain flag
client.publish(
"devices/device001/status" ,
"online" ,
qos = 1 ,
retain = True # Enable message retention
)
print ( "Retained message published" )
client.disconnect()
JavaScript Examples
Simple Retained Publish
Last Will with Retained
Periodic State Updates
const mqtt = require ( 'mqtt' );
const client = mqtt . connect ( 'mqtt://localhost:1883' );
client . on ( 'connect' , () => {
// Publish retained message
client . publish (
'home/livingroom/temperature' ,
'22.5' ,
{ qos: 1 , retain: true },
( err ) => {
if ( ! err ) {
console . log ( 'Retained message published' );
}
client . end ();
}
);
});
Java Examples
Basic Retained Publishing
Device State Manager
import com.hivemq.client.mqtt.mqtt5.Mqtt5BlockingClient;
import com.hivemq.client.mqtt.mqtt5.Mqtt5Client;
import com.hivemq.client.mqtt.datatypes.MqttQos;
public class RetainedPublisher {
public static void main ( String [] args ) {
Mqtt5BlockingClient client = Mqtt5Client . builder ()
. identifier ( "retained_publisher" )
. serverHost ( "localhost" )
. buildBlocking ();
client . connect ();
// Publish with retain flag
client . publishWith ()
. topic ( "devices/status" )
. payload ( "online" . getBytes ())
. qos ( MqttQos . AT_LEAST_ONCE )
. retain ( true ) // Enable retention
. send ();
System . out . println ( "Retained message published" );
client . disconnect ();
}
}
Receiving Retained Messages
Detecting Retained Messages
Python Subscriber
JavaScript Subscriber
Java Subscriber
import paho.mqtt.client as mqtt
def on_connect ( client , userdata , flags , rc ):
print ( "Connected, subscribing to topics" )
client.subscribe( "devices/+/status" , qos = 1 )
def on_message ( client , userdata , msg ):
# Check if message is retained
if msg.retain:
print ( f "[RETAINED] { msg.topic } : { msg.payload.decode() } " )
else :
print ( f "[LIVE] { msg.topic } : { msg.payload.decode() } " )
client = mqtt.Client( "retained_subscriber" )
client.on_connect = on_connect
client.on_message = on_message
client.connect( "localhost" , 1883 )
client.loop_forever()
Clearing Retained Messages
To remove a retained message, publish an empty payload with the retain flag set.
Clear Retained Message
Clear Retained Messages
Mosquitto CLI
import paho.mqtt.client as mqtt
client = mqtt.Client( "clear_retained" )
client.connect( "localhost" , 1883 )
# Clear retained message by publishing empty payload
client.publish(
"devices/device001/status" ,
payload = "" , # Empty payload
qos = 1 ,
retain = True # Retain flag must be true
)
print ( "Retained message cleared" )
client.disconnect()
Bulk Clear Retained Messages
import paho.mqtt.client as mqtt
retained_topics = []
def on_connect ( client , userdata , flags , rc ):
# Subscribe to all topics to discover retained messages
client.subscribe( "#" )
def on_message ( client , userdata , msg ):
if msg.retain:
retained_topics.append(msg.topic)
print ( f "Found retained message: { msg.topic } " )
def clear_all_retained ():
client = mqtt.Client( "bulk_clear" )
client.on_connect = on_connect
client.on_message = on_message
client.connect( "localhost" , 1883 )
client.loop_start()
# Wait to collect all retained messages
time.sleep( 2 )
client.loop_stop()
client.disconnect()
# Clear each retained message
client.connect( "localhost" , 1883 )
for topic in retained_topics:
client.publish(topic, "" , retain = True )
print ( f "Cleared: { topic } " )
client.disconnect()
print ( f "Cleared { len (retained_topics) } retained messages" )
clear_all_retained()
Common Use Cases
Device Status
Configuration
Last Known Value
System State
Track online/offline status of devices: # Device publishes status on connect
client.publish( "devices/sensor001/status" , "online" , retain = True )
# Set Last Will Testament (LWT) to update on disconnect
client.will_set( "devices/sensor001/status" , "offline" , retain = True )
# Monitoring dashboard always sees latest status
Distribute configuration to devices: # Publish configuration once
config = json.dumps({ "interval" : 60 , "threshold" : 25 })
client.publish( "config/sensors" , config, qos = 2 , retain = True )
# New devices get config immediately on subscribe
# No need to republish for every new device
Store last known sensor reading: # Sensor publishes readings
client.publish(
"sensors/temperature/current" ,
"22.5" ,
qos = 1 ,
retain = True
)
# Dashboard shows last value immediately
# Even if sensor hasn't published recently
Track system operating modes: # Publish system mode
modes = [ "NORMAL" , "MAINTENANCE" , "EMERGENCY" ]
current_mode = "NORMAL"
client.publish(
"system/mode" ,
current_mode,
qos = 2 ,
retain = True
)
# All components see current mode on startup
Retained Messages with Last Will Testament
LWT + Retained
Heartbeat with Retained LWT
import paho.mqtt.client as mqtt
import time
def on_connect ( client , userdata , flags , rc ):
print ( "Connected, publishing online status" )
# Publish online status
client.publish(
"devices/sensor001/status" ,
"online" ,
qos = 1 ,
retain = True
)
client = mqtt.Client( "lwt_device" )
# Set Last Will with retain flag
client.will_set(
"devices/sensor001/status" ,
payload = "offline" ,
qos = 1 ,
retain = True # Retained LWT
)
client.on_connect = on_connect
client.connect( "localhost" , 1883 )
client.loop_start()
# Simulate device operation
time.sleep( 10 )
# Ungraceful disconnect triggers LWT
# (In real scenario, this might be network failure)
client.disconnect()
Always set retained LWT messages to properly track device status. Without retention, monitoring systems might miss offline events.
Best Practices
Do:
Use retained messages for state information
Clear retained messages when no longer needed
Set retained LWT for device status
Use QoS 1 or 2 with retained messages for reliability
Keep retained message payloads small
Don’t:
Retain high-frequency telemetry data
Retain temporary or time-sensitive data
Use retained messages for commands
Forget to clear old retained messages
Retain sensitive information without encryption
Retention Policy Example
import paho.mqtt.client as mqtt
import time
import json
class SmartRetentionPublisher :
def __init__ ( self ):
self .client = mqtt.Client( "smart_publisher" )
self .client.connect( "localhost" , 1883 )
self .client.loop_start()
def publish_state ( self , topic , value ):
"""State should be retained"""
self .client.publish(topic, value, qos = 1 , retain = True )
def publish_event ( self , topic , value ):
"""Events should NOT be retained"""
self .client.publish(topic, value, qos = 1 , retain = False )
def publish_telemetry ( self , topic , value ):
"""Telemetry should NOT be retained (high frequency)"""
self .client.publish(topic, value, qos = 0 , retain = False )
publisher = SmartRetentionPublisher()
# Good: Retain device status
publisher.publish_state( "devices/d001/status" , "online" )
# Good: Don't retain events
publisher.publish_event( "devices/d001/events/button_press" ,
json.dumps({ "timestamp" : time.time()}))
# Good: Don't retain high-frequency data
publisher.publish_telemetry( "sensors/temp" , "22.5" )
Memory Usage
# Monitor retained message count
import paho.mqtt.client as mqtt
retained_count = 0
def on_message ( client , userdata , msg ):
global retained_count
if msg.retain:
retained_count += 1
client = mqtt.Client()
client.on_message = on_message
client.connect( "localhost" , 1883 )
client.subscribe( "#" ) # Subscribe to all topics
client.loop_start()
time.sleep( 3 ) # Wait to collect messages
print ( f "Total retained messages: { retained_count } " )
HiveMQ Community Edition stores retained messages in memory. Large numbers of retained messages can impact broker performance.
Testing Retained Messages
# Terminal 1: Publish retained message
mosquitto_pub -h localhost -t "test/retained" -m "Hello Retained" -r -q 1
# Terminal 2: Subscribe and receive retained message immediately
mosquitto_sub -h localhost -t "test/retained" -v
# Output: test/retained Hello Retained
# Terminal 3: Subscribe later (still receives retained message)
mosquitto_sub -h localhost -t "test/retained" -v
# Output: test/retained Hello Retained
# Clear the retained message
mosquitto_pub -h localhost -t "test/retained" -n -r
# Terminal 4: New subscriber receives nothing
mosquitto_sub -h localhost -t "test/retained" -v
# (no output - retained message cleared)
Next Steps
Quality of Service Understand QoS levels with retained messages
Last Will Testament Implement graceful disconnection handling