Choosing between MQTT client libraries is the first real engineering decision in any MQTT project, and the wrong pick shows up later as reconnect bugs, missing MQTT 5 features or a blocked event loop. This guide compares the maintained options for Python, JavaScript, Go, Java, C#, Rust, C, microcontrollers and mobile, and gives you a minimal snippet for each one that connects to the free broker broker.connectmq.com. By the end you will know which library fits your stack and what to check before you commit to it.
What an MQTT Client Library Actually Does
An MQTT client library hides the binary protocol behind a few calls: connect, subscribe, publish, disconnect. Under the hood it does much more, and these hidden jobs are where libraries differ:
- Packet encoding and decoding for MQTT 3.1.1 and, in better libraries, MQTT 5.0 properties such as reason codes, message expiry and user properties.
- Keep-alive pings so the broker knows the connection is healthy.
- QoS state machines that store in-flight QoS 1 and QoS 2 messages and retry them after a reconnect.
- Reconnect logic with backoff, plus resubscribing when the session was not persisted.
- Transport handling for TCP, TLS and WebSocket.
- Threading or async integration with your runtime: a background thread, an event loop, goroutines or callbacks.
A library that gets the protocol encoding right but handles reconnects poorly will pass every demo and fail in the field. Keep that in mind when you read the comparison below. If the broker side is still fuzzy, read what an MQTT broker does first; every library here is just a client of one.
How to Evaluate MQTT Client Libraries
Use this checklist before adopting any library, regardless of language:
- Maintenance. Look at recent releases and how issues are handled. MQTT itself is stable, but TLS stacks, runtimes and platforms move constantly.
- Protocol version. Do you need MQTT 5? Shared subscriptions, message expiry, request/response and detailed reason codes only exist there.
- Transports. Browsers need WebSocket; locked-down corporate networks often need WSS on 443-style ports; devices usually want TLS on 8883.
- Concurrency model. Callback-based, thread-based or async/await. Pick what matches the rest of your codebase, not what looks shortest in a README.
- Reconnect and offline buffering. Does it reconnect automatically? Does it queue publishes while offline, and is that queue bounded?
- Persistence of in-flight messages. Memory only, or file-backed? This matters for QoS 1 and 2 across process restarts.
- Footprint. On microcontrollers, RAM and flash decide everything; on servers, they rarely matter.
- License. Most popular libraries use EPL/EDL, MIT or Apache 2.0, but check before shipping a commercial product.
MQTT Client Libraries at a Glance
| Language / platform | Library | MQTT 5 | Model | Good fit for |
|---|---|---|---|---|
| Python | paho-mqtt 2.x | Yes | Background thread or manual loop | Scripts, gateways, Raspberry Pi |
| Python | aiomqtt | Yes | asyncio | Async services, FastAPI backends |
| JavaScript / TypeScript | MQTT.js 5.x | Yes | Event emitter, promises | Node.js and browsers (WebSocket) |
| Go | Eclipse Paho Go (paho.mqtt.golang) | No (3.1.1) | Tokens, goroutines | Mature 3.1.1 services |
| Go | Eclipse Paho paho.golang | Yes | Context-based | New MQTT 5 services |
| Java / Kotlin | Eclipse Paho Java (mqttv3, mqttv5) | Yes (v5 package) | Sync and async clients | JVM backends, Android |
| Java / Kotlin | HiveMQ MQTT Client | Yes | Blocking, async, reactive | Reactive JVM stacks |
| C# / .NET | MQTTnet | Yes | async/await | ASP.NET Core, MAUI, Unity |
| Rust | rumqttc | Yes | async (tokio) or sync | Edge services, gateways |
| C | Eclipse Paho C, libmosquitto | Yes | Sync and async | Linux devices, native apps |
| Arduino / ESP32 | PubSubClient, AsyncMqttClient | No (3.1.1) | Loop-based / async callbacks | Microcontrollers |
| ESP-IDF | esp-mqtt | Yes | Event handler | Production ESP32 firmware |
| Swift | CocoaMQTT | Yes | Delegate / closures | iOS and macOS apps |
| Dart / Flutter | mqtt_client | No (3.1.1) | Streams | Cross-platform mobile |
The table reflects the libraries' documented capabilities at the time of writing. Always confirm against the project's current release notes, because MQTT 5 support in particular has been arriving in stages. The Eclipse Paho project page at eclipse.dev/paho is a good starting point for the Paho family.
Python: paho-mqtt and aiomqtt
paho-mqtt is the default Python choice and works almost everywhere, from a Raspberry Pi to a container. Version 2.x introduced a breaking change: you must pass a callback API version, and the v2 callbacks receive reason codes and properties. Most "it does not work" reports come from copying 1.x examples into a 2.x install, which is covered in detail in the paho-mqtt 2.0 CallbackAPIVersion fix.
import uuid
import paho.mqtt.client as mqtt
TOPIC = "connectmq/demo/k3x8p1/libs/python"
def on_connect(client, userdata, flags, reason_code, properties):
print("Connected:", reason_code)
client.subscribe(TOPIC, qos=1)
client.publish(TOPIC, "hello from paho-mqtt", qos=1)
def on_message(client, userdata, msg):
print(msg.topic, msg.payload.decode())
client = mqtt.Client(
mqtt.CallbackAPIVersion.VERSION2,
client_id=f"libs-py-{uuid.uuid4().hex[:8]}",
protocol=mqtt.MQTTv5,
)
client.on_connect = on_connect
client.on_message = on_message
client.tls_set()
client.connect("broker.connectmq.com", 8883, keepalive=30)
client.loop_forever()
aiomqtt wraps paho in an idiomatic asyncio API with async with and async for. If your service already runs an event loop, use it instead of mixing threads and coroutines. The full walkthroughs are in our paho-mqtt Python tutorial and the aiomqtt asyncio guide.
JavaScript and TypeScript: MQTT.js
MQTT.js is the de facto library for Node.js and the browser, ships TypeScript types and supports MQTT 5. In Node.js it connects over TCP or TLS; in the browser it uses WebSocket, so the URL scheme is the only thing that changes.
import mqtt from "mqtt";
const clientId = "libs-js-" + Math.random().toString(16).slice(2, 10);
const topic = "connectmq/demo/k3x8p1/libs/javascript";
// Node.js: mqtts://broker.connectmq.com:8883
// Browser: wss://broker.connectmq.com:8084/mqtt
const client = mqtt.connect("mqtts://broker.connectmq.com:8883", {
clientId,
protocolVersion: 5,
});
client.on("connect", async () => {
await client.subscribeAsync(topic, { qos: 1 });
await client.publishAsync(topic, "hello from MQTT.js", { qos: 1 });
});
client.on("message", (t, payload) => console.log(t, payload.toString()));
client.on("error", (err) => console.error(err.message));
MQTT.js reconnects automatically by default (reconnectPeriod controls the delay). In the browser, remember that an HTTPS page can only open wss:// connections. Framework-specific patterns live in MQTT.js with Node.js and MQTT in the browser with JavaScript.
Go: Eclipse Paho
Go has two Eclipse projects. paho.mqtt.golang is the long-established MQTT 3.1.1 client used in a great many production services. paho.golang is the newer MQTT 5 implementation with an autopaho package that manages reconnection for you. For a new MQTT 5 project, start with paho.golang; for broad 3.1.1 compatibility and the largest body of examples, the classic client is still a solid choice.
package main
import (
"crypto/tls"
"fmt"
"math/rand"
"time"
mqtt "github.com/eclipse/paho.mqtt.golang"
)
func main() {
topic := "connectmq/demo/k3x8p1/libs/go"
opts := mqtt.NewClientOptions().
AddBroker("tls://broker.connectmq.com:8883").
SetClientID(fmt.Sprintf("libs-go-%06d", rand.Intn(1000000))).
SetTLSConfig(&tls.Config{MinVersion: tls.VersionTLS12}).
SetAutoReconnect(true)
client := mqtt.NewClient(opts)
if token := client.Connect(); token.Wait() && token.Error() != nil {
panic(token.Error())
}
client.Subscribe(topic, 1, func(_ mqtt.Client, m mqtt.Message) {
fmt.Printf("%s %s\n", m.Topic(), m.Payload())
}).Wait()
client.Publish(topic, 1, false, "hello from Go").Wait()
time.Sleep(2 * time.Second)
client.Disconnect(250)
}
See building a Go MQTT client for graceful shutdown and structured payloads.
Java and Kotlin: Paho and HiveMQ Client
Eclipse Paho Java ships two packages: org.eclipse.paho.client.mqttv3 for MQTT 3.1.1 and org.eclipse.paho.mqttv5.client for MQTT 5. The HiveMQ MQTT Client is an alternative with blocking, async and reactive APIs that suits Reactor or RxJava codebases. Both run on Android too, which is why Kotlin apps often use them directly.
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import java.util.UUID;
public class LibsJava {
public static void main(String[] args) throws Exception {
String topic = "connectmq/demo/k3x8p1/libs/java";
String clientId = "libs-java-" + UUID.randomUUID().toString().substring(0, 8);
MqttClient client = new MqttClient("ssl://broker.connectmq.com:8883",
clientId, new MemoryPersistence());
MqttConnectOptions options = new MqttConnectOptions();
options.setAutomaticReconnect(true);
options.setCleanSession(true);
client.connect(options);
client.subscribe(topic, 1, (t, msg) ->
System.out.println(t + " " + new String(msg.getPayload())));
client.publish(topic, "hello from Paho Java".getBytes(), 1, false);
Thread.sleep(2000);
client.disconnect();
client.close();
}
}
Spring Boot users usually wrap this in Spring Integration; the patterns are in our Java MQTT client guide and the Android MQTT with Kotlin tutorial.
C# and .NET: MQTTnet
MQTTnet is the standard .NET library and supports MQTT 5, TLS and WebSocket. It is fully async and fits ASP.NET Core hosted services, .NET MAUI apps and Unity. Note that the major versions differ: version 5 renamed the factory to MqttClientFactory, while version 4 uses MqttFactory. The snippet targets version 5.
using MQTTnet;
using MQTTnet.Formatter;
var topic = "connectmq/demo/k3x8p1/libs/csharp";
var factory = new MqttClientFactory();
using var client = factory.CreateMqttClient();
client.ApplicationMessageReceivedAsync += e =>
{
Console.WriteLine($"{e.ApplicationMessage.Topic} {e.ApplicationMessage.ConvertPayloadToString()}");
return Task.CompletedTask;
};
var options = new MqttClientOptionsBuilder()
.WithTcpServer("broker.connectmq.com", 8883)
.WithClientId($"libs-cs-{Guid.NewGuid():N}"[..16])
.WithProtocolVersion(MqttProtocolVersion.V500)
.WithTlsOptions(o => o.UseTls())
.Build();
await client.ConnectAsync(options);
await client.SubscribeAsync(factory.CreateSubscribeOptionsBuilder()
.WithTopicFilter(topic).Build());
await client.PublishAsync(new MqttApplicationMessageBuilder()
.WithTopic(topic).WithPayload("hello from MQTTnet").Build());
await Task.Delay(2000);
await client.DisconnectAsync();
MQTTnet does not reconnect on its own in the low-level client, so wrap the connection in a loop or a background service. The MQTTnet tutorial shows a resilient hosted-service pattern.
Rust: rumqttc
rumqttc is the most widely used Rust client. It separates the client handle from an event loop that you poll, which gives you explicit control over back-pressure. Both MQTT 3.1.1 and MQTT 5 modules exist.
use rumqttc::{AsyncClient, Event, MqttOptions, Packet, QoS};
use std::time::Duration;
#[tokio::main]
async fn main() {
let topic = "connectmq/demo/k3x8p1/libs/rust";
let mut opts = MqttOptions::new("libs-rust-k3x8p1", "broker.connectmq.com", 1883);
opts.set_keep_alive(Duration::from_secs(30));
let (client, mut eventloop) = AsyncClient::new(opts, 10);
client.subscribe(topic, QoS::AtLeastOnce).await.unwrap();
client.publish(topic, QoS::AtLeastOnce, false, "hello from rumqttc").await.unwrap();
loop {
match eventloop.poll().await {
Ok(Event::Incoming(Packet::Publish(p))) => {
println!("{} {}", p.topic, String::from_utf8_lossy(&p.payload));
}
Ok(_) => {}
Err(e) => {
eprintln!("connection error: {e}");
tokio::time::sleep(Duration::from_secs(2)).await;
}
}
}
}
Polling the event loop again after an error is how rumqttc reconnects. TLS needs one of its TLS feature flags; the rumqttc guide covers that setup.
C and Embedded: Paho C, libmosquitto, PubSubClient, esp-mqtt
For native Linux applications, Eclipse Paho C offers synchronous and asynchronous APIs with MQTT 5 support, and libmosquitto is a small, battle-tested alternative from the Mosquitto project.
#include <stdio.h>
#include <string.h>
#include "MQTTClient.h"
int main(void) {
MQTTClient client;
MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
MQTTClient_message msg = MQTTClient_message_initializer;
MQTTClient_deliveryToken token;
const char *topic = "connectmq/demo/k3x8p1/libs/c";
char payload[] = "hello from Paho C";
MQTTClient_create(&client, "tcp://broker.connectmq.com:1883", "libs-c-k3x8p1",
MQTTCLIENT_PERSISTENCE_NONE, NULL);
opts.keepAliveInterval = 30;
opts.cleansession = 1;
if (MQTTClient_connect(client, &opts) != MQTTCLIENT_SUCCESS) {
fprintf(stderr, "connect failed\n");
return 1;
}
msg.payload = payload;
msg.payloadlen = (int)strlen(payload);
msg.qos = 1;
msg.retained = 0;
MQTTClient_publishMessage(client, topic, &msg, &token);
MQTTClient_waitForCompletion(client, token, 10000L);
MQTTClient_disconnect(client, 10000);
MQTTClient_destroy(&client);
return 0;
}
On microcontrollers the trade-offs change. PubSubClient is tiny and simple but supports only MQTT 3.1.1, publishes at QoS 0 and has a default packet buffer of 256 bytes that you must raise with setBufferSize() for JSON payloads. AsyncMqttClient adds QoS 1 and 2 publishing with a non-blocking design. For production ESP32 firmware, Espressif's esp-mqtt component in ESP-IDF supports MQTT 5, TLS and WebSocket and is maintained by the chip vendor. Start with the ESP32 MQTT tutorial for working Arduino code, and with Paho MQTT C for Linux devices.
Mobile: Swift, Kotlin, Flutter and React Native
Mobile clients face a constraint servers do not: the operating system suspends background apps and kills idle sockets. Whatever library you pick, plan for frequent reconnects, use persistent sessions or retained messages to catch up, and use push notifications for anything that must reach a sleeping phone.
- iOS / macOS: CocoaMQTT supports MQTT 5, TLS and WebSocket. See MQTT on iOS with Swift.
- Android: Paho Java or the HiveMQ client from Kotlin, ideally inside a lifecycle-aware component.
- Flutter:
mqtt_clientcovers server and browser targets from one Dart codebase. See the Flutter MQTT tutorial. - React Native: MQTT.js over WebSocket is the common route, with a few polyfills. See React Native MQTT.
Pitfalls That Look Like Library Bugs
Most issues blamed on a library are actually configuration or design problems. Check these first:
| Symptom | Real cause | Fix |
|---|---|---|
| Constant connect/disconnect loop | Two processes share one client ID | Generate unique IDs; the broker drops the older session |
| No messages after reconnect | Clean session plus no resubscribe | Resubscribe in the connect callback, not once at startup |
| Event loop freezes | Blocking work inside a message callback | Hand work to a queue, thread pool or async task |
| Large payloads silently dropped | Client buffer or broker size limit | Raise the client buffer; respect broker limits |
| TLS handshake fails on device | Missing root CA or wrong clock | Install the CA bundle and sync time via NTP before connecting |
| Duplicates at QoS 1 | Expected at-least-once behavior | Make handlers idempotent; see MQTT QoS levels explained |
Reconnect handling deserves special attention in Python; the paho-mqtt reconnect guide explains the backoff settings. For a systematic approach to verifying a client before shipping, read testing MQTT clients, and keep mosquitto_pub and mosquitto_sub examples handy as an independent second client when debugging.
Moving From the Public Broker to Your Own
Every snippet above uses the anonymous public broker, which is perfect for comparing libraries side by side: run the Python subscriber, publish from Go, watch the message arrive. The public MQTT test broker is shared and readable by anyone, though, so production clients need credentials and an isolated broker.
ConnectMQ is a fully managed MQTT service that works with every library in this guide, because it speaks standard MQTT 3.1.1 and 5.0 over TCP 1883, TLS 8883, WebSocket 8083 and Secure WebSocket 8084 (path /mqtt). When you create a broker in the portal you receive a username, password and client ID. Switching any snippet above is a three-line change, shown here for paho-mqtt:
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="YOUR_CLIENT_ID", protocol=mqtt.MQTTv5)
client.username_pw_set("YOUR_USERNAME", "YOUR_PASSWORD")
client.tls_set()
ConnectMQ binds client IDs to issued credentials, so use the generated ID exactly as given. If you are still deciding how to host your broker, our guide to the cloud MQTT broker landscape compares serverless, dedicated and bring-your-own-cloud options.
Connect Any MQTT Library to ConnectMQ
Pick the library that matches your stack, run its snippet against broker.connectmq.com, and when it is time for private topics, create a free broker at app.connectmq.com. The Serverless free plan needs no credit card and gives you up to 25 connections, QoS up to 1 and 256 KB messages, which is plenty for building and testing clients in several languages at once.
Frequently Asked Questions
Which MQTT client library is the best?
There is no single best one; the right choice is the maintained library that matches your language and concurrency model. paho-mqtt for Python, MQTT.js for JavaScript, MQTTnet for .NET and Paho or rumqttc for Go and Rust are the common, well-supported defaults.
Do all MQTT client libraries support MQTT 5?
No. Several popular ones, including PubSubClient for Arduino and the classic Paho Go client, only implement MQTT 3.1.1. If you need shared subscriptions, message expiry or reason codes, confirm MQTT 5 support before you adopt a library.
Can clients written in different languages talk to each other?
Yes. The broker routes bytes, not language objects, so a Python publisher and a C# subscriber interoperate as long as they agree on topics and payload format. JSON is the easiest common format to start with.
Which library should I use in the browser?
MQTT.js over Secure WebSocket is the standard choice. Connect to wss://broker.connectmq.com:8084/mqtt, because browsers cannot open raw TCP sockets and HTTPS pages block insecure WebSocket connections.
Why does my client work on the public broker but not on a private one?
Usually it is missing credentials, a client ID that does not match the issued one, or TLS not being enabled. Check the CONNACK reason code your library reports; it tells you exactly which of these is wrong.



