From the device to the event backbone
In many IoT landscapes you come across a familiar starting point: Countless sensors and edge devices speak MQTT – slim, simple, efficient. In addition, there are established infrastructures with established brokers such as Mosquitto, EMQX, HiveMQ or the MQTT stacks from various industrial and gateway manufacturers. These brokers are deeply integrated into existing edge topologies, are often operated with high availability and are part of security-critical communication paths.
At the same time, there is an increasing desire in the backend for streaming, persistence, scaling, Traceability, multi-tenancy and modern evaluation options – something that classic MQTT brokers only partially or not at all.
The question therefore arises:
Why not stream MQTT messages directly into Apache Pulsar - without rebuilding the existing MQTT infrastructure?
It was precisely from this idea that pulsar-iotbridge was born: A compact Go tool that attaches to an existing MQTT broker, subscribed to its topics and all incoming messages reflected into a clean, consistent Pulsar naming structure.
The existing MQTT landscape remains unchanged, while a modern, scalable event system is created in the backend.
Motivation
MQTT is great for device communication, but only to a limited extent Backend processing and analysis. In contrast, Apache Pulsar offers:
- Topics with retention
- horizontal scaling
- Subscription models for real-time and replays
- a clean multi-tenant model
- Backlogs, DLQs, compaction, geo-replication…
Short: MQTT for the edge, Pulsar for the backend - two worlds that complement each other perfectly.
Pulsar is optimized for large streaming workloads, not power-efficient edge devices. MQTT, on the other hand, is made for small devices, supports QoS, is bandwidth-efficient and lightweight.
The combination of both offers the best of both worlds.
Idea
The basic idea of pulsar-iotbridge is deliberately minimalist:
- Connect to an MQTT broker
- Subscribe to all desired topics (by default:
#) - Assign each incoming message to a naming structure
- Publish the payload unchanged in the associated Pulsar topic
From an MQTT topic like:
dt/device123/temperature
becomes a pulsar topic like:
persistent://public/mqtt_ingest/dt/device123/temperature
Without transformation, without magic – simply an event transport between two worlds.
- no semantic interpretation
- no data enrichment
- no schemas
- no transformations
It is deliberately “stupidly simple”. That’s exactly what makes them robust.
Implementation
The focus of the project is not on as many features as possible, but on a comprehensible, maintainable and secure architecture.
Pulsar-iotbridge relies on two proven building blocks:
1. MQTT client (Paho)
The bridge uses Eclipse Paho’s established Go implementation for MQTT. It connects as a client, subscribes to the desired topics and receives every message via a callback.
2. Apache Pulsar Go client
For the backend, the bridge uses the native Pulsar Go client. This offers:
- efficient batching
- LZ4 compression
- asynchronous publishing
- stable connection handling
A publisher is created and reused per Pulsar topic. This creates a clearly defined path per topic - structured and neatly isolated.
Go is great for IO-intensive tools:
- small, static binaries
- fast startup times
- very good parallelization
- low memory footprint
Ideal for lightweight integration components.
Architecture from a bird’s eye view
[ IoT-Device ] --MQTT--> [ MQTT-Broker ] --> [ pulsar-iotbridge ]
|
| Pulsar-Event
v
[ Apache Pulsar ]
|
v
[ Analytics / Backend ]
Local testing with Podman
You can start a Pulsar standalone with just a few lines:
podman run -it --rm \
-p 6650:6650 \
-p 8080:8080 \
docker.io/apachepulsar/pulsar:4.0.7 \
bin/pulsar standalone
An MQTT broker can run in parallel, e.g. E.g. Mosquito.
Starting the bridge
./pulsar-iotbridge run \
--mqtt-url tcp://localhost:1883 \
--pulsar-url pulsar://localhost:6650 \
--tenant public \
--namespace mqtt_ingest \
--workers 8
Now all MQTT messages from all topics are intercepted and mirrored to Pulsar.
pulsar-iotbridge comes with a small Prometheus endpoint (/metrics).
This allows central operational variables to be monitored directly – without additional instrumentation.
The following are recorded, among other things:
- incoming MQTT messages per topic
- successfully published Pulsar messages
- pending Pulsar releases
- Error counter per Pulsar topic
- dropped messages when overloaded
This means you can always keep an eye on whether the bridge is working properly under load and whether topics are “running hot”. or whether incoming queues are reaching their limits. Ideal for dashboards, alerting and capacity planning.
Verify
Receiving page in Pulsar:
./pulsar-cli consumer --regex -t 'persistent://public/mqtt_ingest/.*' -s debug
MQTT test:
mosquitto_pub -t dt/dev1/temp -m "22.4"
A few milliseconds later, the message appears in the Pulsar consumer.
Warnings and limitations
Due to the worker pool and asynchronous publishing, the order of messages from an MQTT topic can differ in the Pulsar topic.
If you need strict ordering semantics, you should:
- operate the bridge with
--workers 1, or - introduce a serialization mechanism per topic, or
- use Pulsar-IO or other integrated mechanisms directly.
The input queue is deliberately limited. If it is full, messages will be dropped instead of putting back pressure on the devices.
This protects the stability of the entire system - but not every application tolerates drops.
Alternative: Pulsar MOP (MQTT-on-Pulsar)
Instead of running an external bridge, Pulsar-MOP extends the Pulsar broker itself with a complete MQTT server. Devices still speak MQTT — but end up directly in the Pulsar ecosystem.
Advantages:
- no separate bridge or additional infrastructure required
- Devices send directly to Pulsar → no media disruption
- full native MQTT compatibility (QoS, Sessions, Retain etc.)
- Data lands immediately in Pulsar topics and benefits from retention, backlog & replays
Disadvantages:
- completely replaces the existing MQTT broker
- higher operational footprint (Pulsar also has to handle MQTT workloads)
- often too heavyweight for resource-limited edge scenarios
- less decoupled than an external bridge (monolithization tendency)
Project page: https://github.com/streamnative/mop
Pulsar-MOP extends the Pulsar broker with a fully integrated MQTT server: Devices continue to speak MQTT - but the messages end up directly in Pulsar.
Conclusion
pulsar-iotbridge is a small but effective tool for existing MQTT landscapes into the event world of Apache Pulsar - without converting devices, without major architectural changes, without heavy connector infrastructure.
pulsar-iotbridge is deliberately implemented as a proof of concept: simple, comprehensible, without unnecessary magic - and with clearly visible boundaries.
The focus is on understandability, not completeness. Current limitations include:
- no guaranteed message order (parallelized workers, async publish)
- Smile-but-drop behavior in case of overload instead of backpressure
- no schema support, no transformations
- no zero-copy path and deliberately minimalist error handling
Nevertheless: The code runs, is transparent and shows the approach in a practical way.
- Project repository: pulsar-iotbridge
Image credit: The Pulsar logo is a registered trademark of the Apache Software Foundation. Use in editorial reporting in accordance with official guidelines.