ESP32 and Arduino

ArduinoMqttClient Library Guide: API, Polling, QoS and Will Messages

A complete guide to the official ArduinoMqttClient library: the connection API, why poll() matters, streaming publish and receive, QoS and retain flags, will messages and every connectError() code.

Code editor showing an ArduinoMqttClient sketch next to an Arduino board publishing MQTT messages
Run this on your own MQTT brokerGet a private, managed MQTT broker on the Free plan and start from ready-made code samples.
Try it free Code samples No card required

ArduinoMqttClient is the official MQTT library maintained by Arduino, and it runs on everything from the UNO R4 WiFi and Nano 33 IoT to MKR boards and ESP32. This guide walks through the ArduinoMqttClient API in the order you will use it: configure, connect, poll, publish, receive and set a will message. Every example connects to a real broker so you can test as you read.

Why Choose ArduinoMqttClient

ArduinoMqttClient wraps any Arduino Client object, such as WiFiClient, WiFiSSLClient, EthernetClient or a cellular client, and adds MQTT 3.1.1 on top. Its standout design choice is that both outgoing and incoming messages are handled as streams, so you never need a buffer the size of your largest message.

Capability ArduinoMqttClient
Protocol MQTT 3.1.1
Publish QoS 0, 1 and 2
Subscribe QoS 0, 1 and 2
Retained messages Yes
Will message Yes, streamed with beginWill() / endWill()
Payload handling Streamed (print() / read())
Event model Polling with poll(), callback on message
Maintainer Arduino

If you already know PubSubClient, the PubSubClient Arduino tutorial is a useful comparison point; the two libraries solve the same problem with different trade-offs.

Installing the Library

Open Tools → Manage Libraries in the Arduino IDE, search for "ArduinoMqttClient" and install it. You also need the networking library for your board:

Board Network library Plain client TLS client
UNO R4 WiFi WiFiS3 WiFiClient WiFiSSLClient
Nano 33 IoT, MKR WiFi 1010 WiFiNINA WiFiClient WiFiSSLClient
ESP32 WiFi (core) WiFiClient WiFiClientSecure
Boards with Ethernet shield Ethernet EthernetClient Not built in

Configuring the Client

Create the network client first, then pass it to MqttClient:

#include <WiFiNINA.h>          // or WiFiS3.h, WiFi.h ...
#include <ArduinoMqttClient.h>

WiFiClient net;
MqttClient mqttClient(net);

Before calling connect(), set the session parameters:

mqttClient.setId("acm-51e09b");                       // client ID, never empty
mqttClient.setUsernamePassword("YOUR_USERNAME", "YOUR_PASSWORD"); // optional
mqttClient.setKeepAliveInterval(30 * 1000L);          // milliseconds!
mqttClient.setConnectionTimeout(10 * 1000L);          // milliseconds
mqttClient.setCleanSession(true);

Note the units: unlike PubSubClient, keep alive and timeout values are in milliseconds. Passing 30 instead of 30000 becomes a keep alive of zero seconds after integer division, which disables keep alive entirely, so dead connections go unnoticed by the broker and your will message never fires.

Connecting and Reading connectError()

connect(host, port) returns 1 on success and 0 on failure. When it fails, connectError() tells you why:

connectError() Constant Meaning and fix
-2 MQTT_CONNECTION_REFUSED Socket could not open: Wi-Fi, DNS, port or TLS problem
-1 MQTT_CONNECTION_TIMEOUT No CONNACK in time: wrong port type or poor link
0 MQTT_SUCCESS Connected
1 MQTT_UNACCEPTABLE_PROTOCOL_VERSION Broker rejected MQTT 3.1.1 (rare)
2 MQTT_IDENTIFIER_REJECTED Empty or disallowed client ID
3 MQTT_SERVER_UNAVAILABLE Broker not accepting connections now
4 MQTT_BAD_USER_NAME_OR_PASSWORD Credentials wrong
5 MQTT_NOT_AUTHORIZED Client not permitted, e.g. client ID does not match credentials

A complete, reusable connection function:

bool connectMqtt(const char* host, int port) {
  Serial.print("Connecting to ");
  Serial.print(host);
  Serial.print(':');
  Serial.println(port);

  if (!mqttClient.connect(host, port)) {
    Serial.print("MQTT connect failed, connectError = ");
    Serial.println(mqttClient.connectError());
    return false;
  }
  Serial.println("MQTT connected");
  return true;
}

For testing, use broker.connectmq.com on port 1883, the public MQTT test broker. It accepts anonymous connections with any unique, non-empty client ID. Keep your topics under a unique prefix such as connectmq/demo/<your-id>/ and publish nothing sensitive.

Why poll() Matters

poll() is the heartbeat of ArduinoMqttClient. Each call:

  1. Reads incoming bytes from the network client and parses complete packets.
  2. Calls your onMessage callback (or makes the message available to parseMessage()).
  3. Answers QoS 1 and QoS 2 handshakes for incoming messages.
  4. Sends a PINGREQ when the keep alive interval is about to expire.

If poll() is not called for longer than the keep alive interval, the broker closes the connection. Avoid delay() calls longer than a fraction of your keep alive, and replace them with millis()-based timing:

unsigned long lastSend = 0;

void loop() {
  mqttClient.poll();
  if (millis() - lastSend >= 10000) {
    lastSend = millis();
    // publish here
  }
}

Publishing Messages

Publishing is a three-step stream:

// Plain QoS 0 message
mqttClient.beginMessage("connectmq/demo/acm-51e09b/temperature");
mqttClient.print(22.7);
mqttClient.endMessage();

// Retained, QoS 1
mqttClient.beginMessage("connectmq/demo/acm-51e09b/status", true, 1);
mqttClient.print("online");
mqttClient.endMessage();

The full signature is beginMessage(topic, retain = false, qos = 0, dup = false). There is also an overload that takes the payload size up front, beginMessage(topic, size, retain, qos, dup). When you know the exact size, use it: the library can then write data straight to the network instead of buffering the whole message first, which matters for large payloads on boards with little RAM.

endMessage() returns 1 on success. For QoS 1 and 2, the library handles the acknowledgement packets during subsequent poll() calls.

Because print() accepts numbers, strings and floats, you can build JSON without a temporary buffer:

mqttClient.beginMessage(topicTelemetry);
mqttClient.print("{\"t\":");
mqttClient.print(temperature, 2);
mqttClient.print(",\"h\":");
mqttClient.print(humidity, 1);
mqttClient.print("}");
mqttClient.endMessage();

Receiving Messages

Subscribe after a successful connect, and again after every reconnect when you use a clean session:

mqttClient.onMessage(onMqttMessage);
mqttClient.subscribe("connectmq/demo/acm-51e09b/cmd/#", 1);

The callback receives only the message size. You read the topic and stream the payload:

void onMqttMessage(int messageSize) {
  String topic = mqttClient.messageTopic();
  Serial.print("Topic: ");
  Serial.print(topic);
  Serial.print(", QoS ");
  Serial.print(mqttClient.messageQoS());
  Serial.print(", retained ");
  Serial.println(mqttClient.messageRetain());

  char payload[128];
  int n = 0;
  while (mqttClient.available() && n < (int)sizeof(payload) - 1) {
    payload[n++] = (char)mqttClient.read();
  }
  payload[n] = '\0';
  Serial.println(payload);
}

Any bytes you do not read are discarded when the next message arrives. That makes oversized messages safe: you simply stop reading at your buffer limit.

As an alternative to the callback, you can call int size = mqttClient.parseMessage(); in loop() and handle messages inline when size is greater than zero. Use one style or the other, not both.

Will Messages

A will message (Last Will and Testament) is published by the broker if your device disconnects without saying goodbye, for example after a power cut or Wi-Fi loss. Configure it before connect():

const char willTopic[] = "connectmq/demo/acm-51e09b/status";

mqttClient.beginWill(willTopic, true, 1);   // retained, QoS 1
mqttClient.print("offline");
mqttClient.endWill();

if (connectMqtt("broker.connectmq.com", 1883)) {
  mqttClient.beginMessage(willTopic, true, 1);
  mqttClient.print("online");
  mqttClient.endMessage();
}

Publishing a retained online on the same topic right after connecting gives every dashboard an accurate presence indicator: online while connected, offline as soon as the broker detects the device is gone, which happens after roughly one and a half keep alive intervals.

A Complete Reconnect-Safe Pattern

Put the pieces together with this checklist:

  1. Connect Wi-Fi and wait for a valid IP address.
  2. Set ID, credentials, keep alive and will.
  3. Connect; on failure print connectError() and retry after a delay that grows with each failure.
  4. On success, register onMessage, subscribe, and publish online retained.
  5. In loop(), check Wi-Fi, check mqttClient.connected(), call poll(), and publish on a millis() timer.

Board-specific versions of this pattern are in the Arduino UNO R4 WiFi MQTT tutorial and the Nano 33 IoT MQTT guide with WiFiNINA. For a broader project, the ESP32 IoT tutorial shows how the device side fits with dashboards and alerts.

Common ArduinoMqttClient Mistakes

  • Calling beginWill() after connect(). The will is part of the CONNECT packet, so it must be configured first.
  • Subscribing only in setup(). After a reconnect with a clean session, the broker has forgotten your subscriptions. Subscribe in your connect function instead.
  • Reusing one client ID on several boards. The broker disconnects the older session every time another board connects, producing an endless reconnect loop.
  • Publishing from inside onMessage. It works, but finish reading the incoming payload first; otherwise the unread bytes are lost.
  • Forgetting endMessage(). Without it nothing is sent, and the next beginMessage() call fails.

Production: TLS and Credentials

For real deployments, switch to a TLS client and port 8883 and authenticate with your own credentials. A managed MQTT broker such as ConnectMQ issues a username, password and client ID for each broker you create in the portal; the client ID is bound to those credentials, so set it exactly with setId(). ConnectMQ's certificate comes from Let's Encrypt, which the Wi-Fi modules on current Arduino boards trust out of the box. Review MQTT security best practices before you ship.

Get Started with ConnectMQ

ConnectMQ is a cloud MQTT broker with MQTT 3.1.1 and 5.0, TLS and WebSockets, plus a portal that includes an in-browser MQTT client, live topic explorer and threshold alerts. It is the easiest way to see what your ArduinoMqttClient sketch is really sending. Create a free Serverless broker at app.connectmq.com, no credit card required.

Frequently Asked Questions

Is ArduinoMqttClient better than PubSubClient?

Neither is universally better. ArduinoMqttClient supports QoS 1 and 2 publishing and streams payloads, while PubSubClient is smaller and has a huge base of examples. Pick ArduinoMqttClient for official Arduino boards and QoS needs.

Does ArduinoMqttClient support MQTT 5?

No, it implements MQTT 3.1.1. Modern brokers serve 3.1.1 and 5.0 clients side by side, so your device can still share topics with MQTT 5 applications.

Why does my ArduinoMqttClient connection drop every minute?

Usually poll() is not called often enough, the keep alive was set in seconds instead of milliseconds, or another client uses the same client ID.

What is the maximum message size?

The library streams payloads, so the limit is mostly your broker's. The public broker at broker.connectmq.com accepts up to 64 KB per message; the free Serverless plan allows 256 KB.

Related articles

ESP-IDF MQTT Example: Build an esp-mqtt Client from Scratch

Build an ESP-IDF project from an empty folder with the esp-mqtt component: Wi-Fi station setup, the MQTT event handler, publish and subscribe, MQTT 5 properties and TLS with the certificate bundle.

8 min read

Using esp-mqtt MQTT 5 Features on ESP32 with ESP-IDF

A hands-on ESP-IDF project that enables MQTT 5.0 in esp-mqtt and uses user properties, message expiry, topic aliases, session expiry and response topics against a real MQTT 5 broker.

7 min read

All articles →

Your private MQTT broker is a few clicks away

Create an account, get your broker credentials and connect your first device — no servers to install, patch or scale.