Broker Testing

Testing MQTT 5 Topic Aliases

How to test MQTT 5 topic aliases: read Topic Alias Maximum, verify aliased publishes, measure bandwidth savings and reproduce Topic Alias invalid errors.

Updated · 8 min read

Testing MQTT 5 Topic Aliases

MQTT 5 topic aliases replace a long topic name with a 2-byte number for the rest of a connection. The first PUBLISH carries both the topic and the alias. Every later PUBLISH sends an empty topic plus the alias. To test them, read the Topic Alias Maximum the broker grants in its CONNACK, publish with and without the topic name, check that subscribers receive the full topic, and reproduce the two error cases: an alias that is out of range, and an alias the broker does not know.

The mechanism itself is covered in MQTT topic aliases. This article is the test plan.

How aliases are negotiated

DirectionWho sets the limitWhereDefault
Client → brokerBrokerTopic Alias Maximum in CONNACK0 (no aliases)
Broker → clientClientTopic Alias Maximum in CONNECT0 (no aliases)

Rules a correct implementation follows:

  • Alias values run from 1 to the negotiated maximum. 0 is never valid.
  • The sender chooses the mapping. Sending a new topic with an existing alias number re-maps it.
  • Mappings are per connection and per direction. They vanish on disconnect, even with a persistent session.
  • A PUBLISH with an empty topic must carry an alias that is already mapped on this connection.

How much does an alias save?

Take a typical telemetry topic, factory/line-3/press-07/telemetry/vibration (43 characters), a 20-byte payload and QoS 1:

FieldFull topicWith alias
Fixed header2 bytes2 bytes
Topic name (2-byte length + text)45 bytes2 bytes (empty)
Packet identifier2 bytes2 bytes
Properties (length + Topic Alias)1 byte4 bytes
Payload20 bytes20 bytes
Total70 bytes30 bytes

That is 40 bytes saved per message, more than half the packet. At one message per second, one device saves about 3.5 MB per day. The saving only matters when topics are long relative to payloads, and when bytes cost money or battery, such as cellular, satellite or LPWAN backhaul. On a LAN with 2 KB payloads it is noise. The packet size calculator shows the numbers for your own topics, and MQTT bandwidth overhead covers the other ways to shrink traffic. TLS adds its own per-record overhead, so measure on the wire if the budget is tight.

Test 1: read the negotiated maximum

Before anything else, log what the broker grants. Brokers differ: some allow many aliases, some only a few, and some disable them (maximum 0). A client that uses aliases without checking will be disconnected on brokers that allow none.

With the Mosquitto CLI, -d prints the CONNACK, but property output varies between versions. A script is more reliable. The test below prints the maximum first and stops if it is 0.

Test 2: aliased publishes arrive intact

This paho-mqtt 2.x script publishes one message with the full topic and alias 1, four more with an empty topic and alias 1, and checks that a normal subscriber receives all five with the full topic name. Then it reconnects and sends the stale alias to see how the broker reacts.

alias_test.py
import time, uuid
import paho.mqtt.client as mqtt
from paho.mqtt.properties import Properties
from paho.mqtt.packettypes import PacketTypes

HOST, PORT = "broker.connectmq.com", 1883
RUN = uuid.uuid4().hex[:8]
TOPIC = f"testmqtt/alias/{RUN}/factory/line-3/press-07/telemetry"
received = []

def client(cid, log):
    c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"{cid}-{RUN}", protocol=mqtt.MQTTv5)
    c.on_connect = lambda cl, u, f, rc, props: log.update(max=getattr(props, "TopicAliasMaximum", 0))
    c.on_disconnect = lambda cl, u, f, rc, props: log.update(disconnect=str(rc))
    return c

def alias_props(alias):
    p = Properties(PacketTypes.PUBLISH)
    p.TopicAlias = alias
    return p

# Subscriber: a normal client that does not accept aliases from the broker
sub_log = {}
sub = client("alias-sub", sub_log)
sub.on_message = lambda cl, u, msg: received.append((msg.topic, msg.payload.decode()))
sub.connect(HOST, PORT)
sub.loop_start()
sub.subscribe(TOPIC, qos=1)

# Test 1: negotiation + valid alias use
log = {}
pub = client("alias-pub", log)
pub.connect(HOST, PORT)
pub.loop_start()
time.sleep(1)
print("broker Topic Alias Maximum:", log.get("max"))
if not log.get("max"):
    raise SystemExit("broker does not accept topic aliases from clients (maximum 0)")

pub.publish(TOPIC, "m0", qos=1, properties=alias_props(1))      # set alias 1 = TOPIC
for i in range(1, 5):
    pub.publish("", f"m{i}", qos=1, properties=alias_props(1))  # topic omitted, alias only
time.sleep(2)
ok = received == [(TOPIC, f"m{i}") for i in range(5)]
print("PASS" if ok else "FAIL", "aliased messages arrive with the full topic:", len(received), "of 5")
pub.disconnect(); pub.loop_stop()

# Test 2: alias mappings do not survive a reconnect
received.clear()
log = {}
pub = client("alias-pub", log)
pub.connect(HOST, PORT)
pub.loop_start()
time.sleep(1)
pub.publish("", "after-reconnect", qos=1, properties=alias_props(1))  # unknown on this connection
time.sleep(2)
print("PASS" if not received else "FAIL", "stale alias after reconnect is not delivered;",
      "broker reaction:", log.get("disconnect", "no DISCONNECT, message dropped"))
pub.disconnect(); pub.loop_stop()

# Test 3: alias above the negotiated maximum (only possible if the maximum is below 65535)
if log.get("max", 0) < 65535:
    log2 = {}
    bad = client("alias-bad", log2)
    bad.connect(HOST, PORT)
    bad.loop_start()
    time.sleep(1)
    bad.publish(TOPIC, "too-high", qos=1, properties=alias_props(log2["max"] + 1))
    time.sleep(2)
    print("broker reaction to alias > maximum:", log2.get("disconnect", "none"))
    bad.loop_stop()

sub.disconnect(); sub.loop_stop()

A typical run prints:

Output
broker Topic Alias Maximum: 65535
PASS aliased messages arrive with the full topic: 5 of 5
PASS stale alias after reconnect is not delivered; broker reaction: no DISCONNECT, message dropped

paho-mqtt does not manage aliases for you. It sends exactly the alias you set and does not resolve aliases coming from the broker, so never advertise a Topic Alias Maximum on a paho subscriber. That also makes it a good tool for negative tests. paho does refuse alias 0 locally (TopicAlias property value must be in the range 1-65535), so to test the broker's handling of alias 0 you need a raw packet tool.

Automatic aliases with MQTT.js

MQTT.js 5 can assign aliases for you. With autoAssignTopicAlias it maps topics to aliases on a least-recently-used basis, within the broker's maximum. Log the packets to verify what goes over the wire:

alias.mjs
import mqtt from "mqtt";

const topic = "testmqtt/alias/js-demo/factory/line-3/press-07/telemetry";
const client = mqtt.connect("mqtt://broker.connectmq.com:1883", {
  protocolVersion: 5,
  clientId: `alias-js-${Math.random().toString(16).slice(2, 10)}`,
  autoAssignTopicAlias: true,             // let MQTT.js pick and reuse aliases
  properties: { topicAliasMaximum: 10 },  // accept up to 10 aliases from the broker
});

// Log what actually goes over the wire
client.on("packetsend", (p) => {
  if (p.cmd === "publish") console.log(`sent topic="${p.topic}" alias=${p.properties?.topicAlias}`);
});

client.on("connect", (connack) => {
  console.log("broker Topic Alias Maximum:", connack.properties?.topicAliasMaximum ?? 0);
  let n = 0;
  const timer = setInterval(() => {
    client.publish(topic, JSON.stringify({ n, vibration: Math.random() }), { qos: 1 });
    if (++n === 5) {
      clearInterval(timer);
      setTimeout(() => client.end(), 500);
    }
  }, 300);
});

client.on("error", (err) => console.error("error:", err.message));

Expected output: the first line shows the full topic with alias=1, the next four show topic="" with alias=1. If every line shows the full topic, the broker granted a maximum of 0 and MQTT.js quietly fell back to normal publishes. That is correct behavior, but it means you are not saving anything. autoUseTopicAlias is the more conservative option: it only reuses aliases you registered yourself.

Test 3: the error cases

What you sendSpec saysError people search for
Alias 0Protocol errorDISCONNECT 0x94 Topic Alias invalid
Alias above the negotiated maximumProtocol errorDISCONNECT 0x94 Topic Alias invalid
Empty topic, alias never mappedProtocol errorDISCONNECT 0x82 Protocol Error
Any alias when the maximum is 0Protocol error0x94, or client-side Sending Topic Alias out of range in MQTT.js
Stale alias after reconnectSame as "never mapped"0x82, or the message silently disappears

Brokers do not all react the same way. Some send the DISCONNECT with the reason code, others close the connection or drop the single message without telling you. The test run above shows a broker that dropped the stale-alias message without disconnecting. Your test should therefore assert the important outcome, the bad message was not delivered under a wrong topic, and record the broker's reaction as information. Decode any reason code with the reason code lookup. The fixes for each case are in Topic Alias invalid errors and MQTT protocol error 0x82.

Verifying the subscriber side

Subscribers should never notice aliases unless they opted in. Open the online MQTT client, subscribe to testmqtt/alias/#, and run either script: every message must show the complete topic. If a subscriber that did not advertise a Topic Alias Maximum ever receives an empty topic, the broker is broken. That is a rare but serious bug worth reporting.

Watch aliased messages arrive with full topics

Checklist

  1. Log the Topic Alias Maximum from every CONNACK, and never exceed it.
  2. Send the full topic with the alias first, then alias-only publishes, and assert subscribers see full topics.
  3. Reconnect and confirm that stale aliases are not used.
  4. Try alias above the maximum and an unmapped alias, and record the broker's reaction.
  5. Measure the real byte savings for your topics before making aliases a requirement.

Topic aliases are a cheap win on constrained links and pointless elsewhere. Test the negotiation and the reconnect case, and they will save bandwidth without becoming the reason a device goes silent.

Frequently asked questions

What is the default Topic Alias Maximum?

0. If the CONNECT or CONNACK does not contain the Topic Alias Maximum property, the receiver accepts no aliases at all. Each direction is negotiated separately: the client's value limits the broker, the broker's value limits the client.

Do topic aliases survive a reconnect?

No. Alias mappings exist only for the lifetime of one network connection, even when the session is persistent. After every reconnect the client must send the full topic name again before it can use the alias.

Do subscribers see the alias?

Only if they asked for it. Aliases are per hop: the broker resolves the publisher's alias to the full topic, and only uses aliases toward a subscriber that advertised its own Topic Alias Maximum. Most subscribers simply receive the full topic name.