MQTT topics and wildcards are far more flexible than most people realize, often leading to rigid, unmanageable hierarchies.
Let’s see how this plays out in a real scenario. Imagine a smart home system. We have sensors for temperature, humidity, and light, spread across different rooms.
home/livingroom/temperature
home/livingroom/humidity
home/livingroom/light
home/kitchen/temperature
home/kitchen/humidity
home/kitchen/light
home/bedroom/temperature
home/bedroom/humidity
home/bedroom/light
This is a common, but quickly cumbersome, way to structure things. What if we add a new sensor type, like a CO2 sensor? We’d have to update every single room:
home/livingroom/temperature
home/livingroom/humidity
home/livingroom/light
home/livingroom/co2 <-- Added
home/kitchen/temperature
home/kitchen/humidity
home/kitchen/light
home/kitchen/co2 <-- Added
home/bedroom/temperature
home/bedroom/humidity
home/bedroom/light
home/bedroom/co2 <-- Added
And if we add a new room, say the "hallway"? More additions. This "one-to-one" mapping of sensor type to location is brittle.
The core problem this solves is scaling and flexibility in message routing. MQTT brokers use topics to route messages from publishers to subscribers. A well-designed topic structure ensures that only relevant clients receive messages, and that new devices or data points can be added without a complete overhaul.
Internally, the MQTT broker maintains a topic tree. When a client publishes a message, the broker traverses this tree to find all subscribed clients whose topic filters match the published topic. The wildcards # (multi-level) and + (single-level) are the keys to making this tree efficient.
Consider the previous example. We can re-architect this using wildcards. Instead of specific room-sensor pairs, we can group by location and then by sensor type.
home/+/temperature // Subscribe to all temperature sensors
home/+/humidity // Subscribe to all humidity sensors
home/+/light // Subscribe to all light sensors
Here, the + wildcard in home/+/temperature means "match any single-level directory." So, this subscription would receive messages from home/livingroom/temperature, home/kitchen/temperature, and home/bedroom/temperature.
If we want to subscribe to all sensor data from a specific room, say the living room, we’d use the multi-level wildcard #:
home/livingroom/# // Subscribe to all topics under home/livingroom
This single subscription would receive home/livingroom/temperature, home/livingroom/humidity, and home/livingroom/light.
Now, what if we wanted to subscribe to all temperature readings across the entire house, regardless of room?
home/+/temperature
If we wanted all data from all rooms, we’d use:
home/#
This is incredibly powerful. If we add a new room, like home/bathroom, and publish home/bathroom/temperature, any subscriber using home/+/temperature will automatically receive it. No changes needed on the subscriber side.
The true power of wildcards comes when you combine them and think about the data rather than just the device. The structure home/location/sensor_type is good, but what if we want to aggregate data across all sensors of the same type, regardless of location? Or all data from a specific location?
Let’s say we have a central monitoring service that needs to know the temperature in every room. Instead of subscribing to home/livingroom/temperature, home/kitchen/temperature, etc., it subscribes to home/+/temperature. This single subscription covers all current and future rooms.
Now, consider a different scenario: a security system that needs to know about any motion detected in the entire house. We might structure our topics like this:
security/motion/livingroom
security/motion/kitchen
security/motion/bedroom
A subscriber interested in any motion would use security/motion/#.
A common mistake is to use # too broadly, like subscribing to just #. This means "subscribe to everything." While useful for debugging or a central logging service, it’s generally not what you want for typical application logic, as it can lead to receiving a flood of irrelevant messages and increased network traffic and processing load.
The most surprising truth is that the topic structure is a form of implicit routing logic. The broker doesn’t just pass messages; it actively filters them based on these hierarchical strings and wildcard patterns, effectively creating a dynamic, code-free routing table.
When designing your topics, think about the consumers of the data first. What patterns will they need to subscribe to? Do they need data from a specific device? A specific room? A specific sensor type across all rooms? Or all data from a specific room? Designing around these subscription patterns, rather than just a rigid device-to-topic mapping, unlocks the full potential of MQTT’s pub/sub model.
The next logical step is to understand Quality of Service (QoS) levels and how they interact with topic subscriptions.