Apache NiFi connects to MQTT with two standard processors: ConsumeMQTT subscribes to a topic filter and turns incoming messages into FlowFiles, and PublishMQTT sends FlowFile content to a topic. The minimum configuration is a Broker URI such as tcp://broker.connectmq.com:1883, a Topic Filter or Topic, a QoS and — on ConsumeMQTT — a Max Queue Size. Everything else (TLS, credentials, sessions, records, clustering) decides whether the flow is reliable in production.
This tutorial walks through each property that matters, then builds and tests a flow that consumes JSON telemetry, filters it with SQL and publishes alerts back to MQTT.
Start NiFi for testing
NiFi 2.x runs on Java 21; the official Docker image is the quickest way to get a UI:
docker run --name nifi -p 8443:8443 \
-e SINGLE_USER_CREDENTIALS_USERNAME=admin \
-e SINGLE_USER_CREDENTIALS_PASSWORD=change-me-to-12-chars-min \
-d apache/nifi:latestOpen https://localhost:8443/nifi (accept the self-signed certificate) and log in. Pin a specific image tag for anything you keep. The password must be at least 12 characters, or the container refuses to start.
ConsumeMQTT properties
Drag a processor onto the canvas, search for ConsumeMQTT and open its properties. These are the ones you set on every flow:
| Property | Example | Notes |
|---|---|---|
| Broker URI | tcp://broker.connectmq.com:1883 | Schemes tcp, ssl, ws, wss. A comma-separated list gives failover between brokers. |
| MQTT Specification Version | v5.0 | AUTO tries 5.0 and falls back. Pin the version you tested with. |
| Client ID | (empty) | A UUID is generated when empty — the safe choice in clusters. |
| Group ID | (empty) or nifi-ingest | When set, NiFi subscribes through a shared subscription ($share/<group>/<filter>). |
| Topic Filter | testmqtt/nifi/+/telemetry | One filter; + and # allowed. Test it with the topic matcher. |
| Quality of Service (QoS) | 1 | Use 1 for data you must not silently lose. |
| Max Queue Size | 1000 | In-memory buffer between the MQTT callback and the processor's run. |
| Username / Password | $MQTT_USER / $MQTT_PASS | Use a NiFi parameter (sensitive) instead of typing the secret into the processor. |
| Session state | Clean Session | Resume Session keeps the subscription and queued QoS 1/2 messages across reconnects. |
The processor writes each message to the Message relationship and adds these attributes: mqtt.broker, mqtt.topic, mqtt.qos, mqtt.isDuplicate and mqtt.isRetained. Without a record reader, each MQTT message becomes one FlowFile, and the FlowFile content is the raw payload.
Batching with records
One FlowFile per message is fine for a few messages per second and wasteful beyond that. Set Record Reader to a JsonTreeReader and Record Writer to a JsonRecordSetWriter, and ConsumeMQTT writes all messages received since the last run as records in a single FlowFile. With Add attributes as fields set to true, every record also gets _topic, _qos, _isDuplicate and _isRetained fields, so you do not lose the topic when messages from many devices share one FlowFile. Payloads that the reader cannot parse go to the parse.failure relationship — route it to a funnel or a log so bad devices are visible; see JSON parse errors for typical causes.
TLS
Change the URI to ssl://broker.connectmq.com:8883 (or wss://broker.connectmq.com:8084/mqtt through proxies that only allow HTTPS) and set SSL Context Service to a StandardSSLContextService. For a broker with a publicly trusted certificate, the truststore can point at the JVM's cacerts file; for a private CA, import the CA into a dedicated truststore. If the handshake fails, compare with an openssl s_client test — testing an MQTT TLS connection shows the commands.
PublishMQTT properties
PublishMQTT shares the connection properties (Broker URI, version, Client ID, credentials, SSL) and adds:
| Property | Example | Notes |
|---|---|---|
| Topic | testmqtt/nifi/alerts | Supports Expression Language, e.g. ${mqtt.topic:replace('telemetry','echo')} |
| Quality of Service (QoS) | 1 | Also supports Expression Language |
| Retain Message | false | true only for state topics |
| Record Reader / Record Writer | JsonTreeReader / JsonRecordSetWriter | When set, each record becomes a separate MQTT message |
| Message Expiry Interval | 300 sec | MQTT 5 only; the broker drops the message if undelivered after that time |
Without a record reader, the whole FlowFile content is one payload. Check the size against your broker's maximum packet size — a 5 MB merged FlowFile sent as one MQTT message is a common accident. The packet size calculator helps estimate overhead.
Failed publishes go to failure. Route that relationship back to PublishMQTT with a retry or penalty so a broker blip does not drop data.
Build a tested flow
The goal: consume telemetry from testmqtt/nifi/<device>/telemetry, keep everything for storage, and publish an alert when the temperature exceeds 30.
ConsumeMQTT ──Message──▶ QueryRecord ──hot──▶ PublishMQTT (testmqtt/nifi/alerts)
│ └──original──▶ PutDatabaseRecord / PublishKafka / PutFile
└──parse.failure──▶ LogAttribute- ConsumeMQTT with the properties above, Record Reader
JsonTreeReader, Record WriterJsonRecordSetWriter, Add attributes as fieldstrue. - QueryRecord with the same reader/writer and a dynamic property named
hot:
SELECT _topic, device, temperature, ts
FROM FLOWFILE
WHERE temperature > 30- PublishMQTT on the
hotrelationship, Topictestmqtt/nifi/alerts, QoS 1, with a Record Reader/Writer so every hot reading is published as its own message. - Send the
originalrelationship to your storage: PostgreSQL with PutDatabaseRecord, Kafka with PublishKafka, or simply PutFile while you test.
By default QueryRecord also sends an empty FlowFile to hot when nothing matches. Set Include Zero Record FlowFiles to false so only batches with at least one hot reading reach PublishMQTT.
Test it end to end
Start all processors, then open a subscriber on the alert topic:
mosquitto_sub -h broker.connectmq.com -p 1883 -t 'testmqtt/nifi/alerts' -vPublish one normal and one hot reading:
mosquitto_pub -h broker.connectmq.com -p 1883 -q 1 \
-t testmqtt/nifi/press-7/telemetry \
-m '{"device":"press-7","temperature":24.1,"ts":1760700000}'
mosquitto_pub -h broker.connectmq.com -p 1883 -q 1 \
-t testmqtt/nifi/press-7/telemetry \
-m '{"device":"press-7","temperature":33.8,"ts":1760700005}'Terminal 1 should print exactly one alert containing "temperature":33.8 and "_topic":"testmqtt/nifi/press-7/telemetry". In NiFi, right-click the queue after ConsumeMQTT and choose List queue (or open Data Provenance) to see both FlowFiles and their mqtt.* attributes. Then publish not json to the same topic and confirm it lands on parse.failure instead of crashing the flow. You can do the same from the online MQTT client if you prefer a browser.
Watch the NiFi test topics live
Running ConsumeMQTT in a cluster
In a NiFi cluster every node runs every processor. For ConsumeMQTT that means every node subscribes — and every node receives every message. Pick one of two patterns:
- Primary node only. In the processor's Scheduling tab set Execution to Primary node. Simple and ordered, but one node does all MQTT work and there is a short gap when the primary changes.
- Shared subscription. Set Group ID (for example
nifi-ingest) and leave Client ID empty so each node gets a unique ID. The broker distributes messages across nodes; see MQTT shared subscriptions. Ordering is then only per node, not global.
Never set the same fixed Client ID on all nodes: the broker allows one connection per client ID, so the nodes take the session away from each other in a loop.
Sessions, QoS and restarts
With Clean Session and QoS 0, anything published while NiFi is stopped is gone. For ingestion you cannot afford to lose, use QoS 1, a fixed Client ID per consumer, Resume Session and — on MQTT 5 — a Session Expiry Interval long enough to cover maintenance windows. The broker then queues messages during the outage and delivers them when NiFi reconnects. This requires a broker that keeps persistent sessions; the shared test endpoint broker.connectmq.com does not store offline sessions, so verify this behaviour against your own private broker instance (see persistent sessions).
Common errors
| Message or symptom | Cause | Fix |
|---|---|---|
Not authorized to connect (5) | Wrong credentials or no CONNECT permission | Test the same login with mosquitto_sub, then check the sensitive parameter |
Connection lost (32109) repeating | Duplicate client ID or keep-alive timeout | Leave Client ID empty or make it unique per node |
MqttException: ... SSL / handshake errors | Missing truststore, wrong port for ssl:// | Use port 8883 with ssl:// and set the SSL Context Service |
| Back pressure on the outgoing queue | Downstream slower than MQTT input | Enable records, add back-pressure thresholds, scale with shared subscriptions |
Look up numeric reason codes with the MQTT reason codes tool.
NiFi's two MQTT processors are simple, but production flows depend on the details: TLS and parameterised credentials, records instead of one FlowFile per message, a clear clustering strategy and a session setup that matches how much data you can afford to lose. Test every flow with a second MQTT client before trusting it with real devices.
Frequently asked questions
Does ConsumeMQTT support MQTT 5?
Yes. Current NiFi versions have an 'MQTT Specification Version' property with AUTO, v3.1.0, v3.1.1 and v5.0. With v5.0 you also get MQTT 5 settings such as the session expiry interval on ConsumeMQTT and message expiry on PublishMQTT.
Why does every NiFi cluster node receive the same MQTT message?
Each node runs its own ConsumeMQTT instance with its own subscription. Either schedule the processor on the primary node only, or set a Group ID so the nodes join one MQTT shared subscription and the broker splits the messages between them.
How do I batch MQTT messages into one FlowFile?
Set a Record Reader and Record Writer on ConsumeMQTT (for example JsonTreeReader and JsonRecordSetWriter). Messages received between two runs are then written as records into a single FlowFile instead of one FlowFile per message.



