Skip to main content

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

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

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

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

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.
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

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

Retained Messages with Last Will Testament

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")

Performance Considerations

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

Build docs developers (and LLMs) love