MQTT
Point your MQTT 3.1.1 / 5.0 app at KubeMQ by changing only the broker address — all five KubeMQ patterns over the native MQTT wire on ports 1883 / 8883 / 8083.
Point your MQTT 3.1.1 / 5.0 application at KubeMQ by changing only the broker address. The MQTT connector is a built-in, wire-protocol bridge inside kubemq-server: an embedded MQTT broker that speaks the standard protocol natively, so any off-the-shelf MQTT client (paho, MQTT.js, MQTTnet, rumqttc) reaches KubeMQ's Events, Events-Store, Queues, Commands, and Queries with no KubeMQ SDK, no library swap, and no code rewrite.
What is the MQTT connector
MQTT is a lightweight publish/subscribe protocol built around topics: a client connects, subscribes to topic filters, and publishes messages with a chosen Quality of Service (QoS 0/1/2). The KubeMQ MQTT connector exposes this protocol on its own dedicated ports and maps it onto all five KubeMQ messaging patterns.
The first segment of the MQTT topic selects the KubeMQ pattern, and the remaining
segments become the KubeMQ channel with / translated to .. A publish to
events/site1/temp lands on the Events channel site1.temp; queues/jobs targets a
KubeMQ Queue; commands/restart and queries/status drive RPC. The connector is a
gateway, not a client library — your application only needs a stock MQTT client.
Key capabilities:
- All five patterns over one wire — Events, Events-Store, Queues, Commands, and Queries, selected by the topic prefix.
- Topic-driven pattern routing —
events/,store/,queues/,commands/,queries/prefixes map a topic to a KubeMQ pattern; a bare (prefixless) topic routes to the configuredDefaultPattern(eventsby default). - MQTT 3.1.1 and 5.0 — both protocol levels on the same listener; MQTT 5.0 unlocks User-Properties (carried as KubeMQ Tags) and RPC (Commands / Queries).
- Cross-protocol interop — a message published over MQTT to
events/it/crossis consumable by a gRPC or REST KubeMQ client on channelit.cross, and vice-versa.
Retain is silently dropped. The connector forces RetainAvailable=0 in CONNACK. A
runtime publish with the retain flag set returns a success PUBACK (0x00) but the message
is stripped and never delivered or stored. A Will-retain requested at CONNECT time is
rejected outright with CONNACK 0x9A. There are also no durable subscriptions — MQTT
sessions are in-memory and node-local. See
QoS and sessions.
How it works
An MQTT client connects to the connector and publishes to (or subscribes to) a topic. The
connector resolves the topic prefix to a KubeMQ (pattern, channel) pair, hands the
message to the message broker, and consumers on the same channel — over MQTT or any other
KubeMQ transport — receive it.
The embedded broker accepts a standard MQTT connection, the topic mapper resolves the prefix to a KubeMQ pattern and channel, and the message broker fans it out to consumers on any transport.
Ports & protocol surface
| Port | Transport | Protocol | Notes |
|---|---|---|---|
1883 | Plain TCP | MQTT 3.1.1 & 5.0 | Default listener. Disable by setting CONNECTORSMQTT_PORT="". |
8883 | TLS over TCP | MQTT 3.1.1 & 5.0 | Binds only when the server-global Security block (cert + key) is configured; otherwise the port is open but the listener is inactive. |
8083 | WebSocket (path /) | MQTT 3.1.1 & 5.0 | MQTT-over-WebSocket at path /. Upgraded to wss:// when Security is configured. |
Clients select the transport by URL scheme: tcp://host:1883, tls://host:8883, or
ws://host:8083/. The examples read one environment variable, KUBEMQ_MQTT_URL (default
tcp://localhost:1883). MQTT 3.1 (protocol level 3) is always rejected; the minimum
accepted level is 3.1.1. See Configuration for the
listener and capability settings.
Publish an event
The example below connects an MQTT 5.0 client and publishes one message to
events/<channel> at QoS 1. The topic prefix events/ selects the Events pattern, and
/ becomes . in the channel, so events/demo/x lands on the KubeMQ channel demo.x.
Every client reads the broker endpoint from KUBEMQ_MQTT_URL (default
tcp://localhost:1883).
package main
import (
"context"
"fmt"
"log"
"net"
"os"
"strings"
"time"
"github.com/eclipse/paho.golang/paho"
)
func brokerURL() string {
if u := os.Getenv("KUBEMQ_MQTT_URL"); u != "" {
return u
}
return "tcp://localhost:1883"
}
// tcpAddr strips the scheme prefix to obtain "host:port".
func tcpAddr(rawURL string) string {
for _, pfx := range []string{"tcp://", "ws://", "tls://"} {
if strings.HasPrefix(rawURL, pfx) {
return rawURL[len(pfx):]
}
}
return rawURL
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
// Prefix "events/" selects the Events pattern; '/' becomes '.' so
// "events/demo/x" maps to the KubeMQ channel "demo.x".
const topic = "events/demo/x"
conn, err := net.Dial("tcp", tcpAddr(brokerURL()))
if err != nil {
log.Fatalf("dial: %v", err)
}
client := paho.NewClient(paho.ClientConfig{Conn: conn})
connAck, err := client.Connect(ctx, &paho.Connect{
ClientID: "go-mqtt-events-pub",
KeepAlive: 30,
CleanStart: true,
})
if err != nil {
log.Fatalf("connect: %v", err)
}
if connAck.ReasonCode != 0 {
log.Fatalf("CONNACK reason=0x%02X", connAck.ReasonCode)
}
// QoS 1 returns a PUBACK. Do NOT set Retain — a retained publish is
// silently dropped (PUBACK 0x00, message never delivered).
pubAck, err := client.Publish(ctx, &paho.Publish{
Topic: topic,
QoS: 1,
Payload: []byte("hello from MQTT"),
Properties: &paho.PublishProperties{
// MQTT 5.0 User Properties round-trip as KubeMQ Tags.
User: []paho.UserProperty{{Key: "sensor", Value: "thermometer"}},
},
})
if err != nil {
log.Fatalf("publish: %v", err)
}
if pubAck.ReasonCode != 0 {
log.Fatalf("PUBACK reason=0x%02X", pubAck.ReasonCode)
}
fmt.Printf("published to %q (channel demo.x)\n", topic)
_ = client.Disconnect(&paho.Disconnect{ReasonCode: 0})
}import os
import paho.mqtt.client as mqtt
from paho.mqtt.enums import CallbackAPIVersion
from paho.mqtt.properties import Properties
from paho.mqtt.packettypes import PacketTypes
def parse_url(url: str) -> tuple[str, int]:
scheme, rest = url.split("://", 1)
host_port = rest.rstrip("/")
if ":" in host_port:
host, port = host_port.rsplit(":", 1)
return host, int(port)
return host_port, {"tcp": 1883, "tls": 8883, "ws": 8083}.get(scheme, 1883)
def main() -> None:
url = os.environ.get("KUBEMQ_MQTT_URL", "tcp://localhost:1883")
host, port = parse_url(url)
# Prefix "events/" selects the Events pattern; "/" becomes "." so
# "events/demo/x" maps to the KubeMQ channel "demo.x".
topic = "events/demo/x"
# paho-mqtt 2.x requires an explicit callback API version.
client = mqtt.Client(
callback_api_version=CallbackAPIVersion.VERSION2,
client_id="python-mqtt-events-pub",
protocol=mqtt.MQTTv5,
)
client.connect(host, port, keepalive=30, clean_start=True)
client.loop_start()
# MQTT 5.0 User Properties round-trip as KubeMQ Tags.
props = Properties(PacketTypes.PUBLISH)
props.UserProperty = [("sensor", "thermometer")]
# Do not set retain=True — a retained publish is silently dropped.
info = client.publish(topic, payload=b"hello from MQTT", qos=1, properties=props)
info.wait_for_publish(timeout=10)
print(f"published to {topic!r} (channel demo.x)")
client.loop_stop()
client.disconnect()
if __name__ == "__main__":
main()import java.nio.charset.StandardCharsets;
import java.util.List;
import org.eclipse.paho.mqttv5.client.MqttAsyncClient;
import org.eclipse.paho.mqttv5.client.MqttConnectionOptions;
import org.eclipse.paho.mqttv5.client.persist.MemoryPersistence;
import org.eclipse.paho.mqttv5.common.MqttMessage;
import org.eclipse.paho.mqttv5.common.packet.MqttProperties;
import org.eclipse.paho.mqttv5.common.packet.UserProperty;
public final class Main {
public static void main(String[] args) throws Exception {
String broker = System.getenv().getOrDefault("KUBEMQ_MQTT_URL", "tcp://localhost:1883");
// Prefix "events/" selects the Events pattern; "/" becomes "." so
// "events/demo/x" maps to the KubeMQ channel "demo.x".
String topic = "events/demo/x";
MqttConnectionOptions opts = new MqttConnectionOptions();
opts.setCleanStart(true);
opts.setKeepAliveInterval(30);
// org.eclipse.paho.mqttv5.client always uses MQTT protocol level 5.
MqttAsyncClient client = new MqttAsyncClient(broker, "java-mqtt-events-pub", new MemoryPersistence());
client.connect(opts).waitForCompletion(10_000);
MqttMessage msg = new MqttMessage("hello from MQTT".getBytes(StandardCharsets.UTF_8));
msg.setQos(1);
// Do NOT call msg.setRetained(true) — a retained publish is silently dropped.
// MQTT 5.0 User Properties round-trip as KubeMQ Tags.
MqttProperties props = new MqttProperties();
props.setUserProperties(List.of(new UserProperty("sensor", "thermometer")));
msg.setProperties(props);
client.publish(topic, msg).waitForCompletion(10_000);
System.out.printf("published to %s (channel demo.x)%n", topic);
client.disconnect().waitForCompletion(5_000);
client.close();
}
}import mqtt, { type MqttClient } from "mqtt";
function brokerUrl(): string {
return process.env["KUBEMQ_MQTT_URL"] ?? "tcp://localhost:1883";
}
async function main(): Promise<void> {
// Prefix "events/" selects the Events pattern; "/" becomes "." so
// "events/demo/x" maps to the KubeMQ channel "demo.x".
const topic = "events/demo/x";
const client: MqttClient = mqtt.connect(brokerUrl(), {
clientId: "js-mqtt-events-pub",
protocolVersion: 5,
clean: true,
keepalive: 30,
});
await new Promise<void>((resolve, reject) => {
client.once("connect", () => resolve());
client.once("error", reject);
});
await new Promise<void>((resolve, reject) => {
client.publish(
topic,
"hello from MQTT",
{
qos: 1,
retain: false, // retain is NOT supported; a retained publish is silently dropped.
// MQTT 5.0 User Properties round-trip as KubeMQ Tags.
properties: { userProperties: { sensor: "thermometer" } },
},
(err) => (err ? reject(err) : resolve()),
);
});
console.log(`published to "${topic}" (channel demo.x)`);
await client.endAsync();
}
main().catch((err) => {
console.error(err);
process.exit(1);
});using System.Text;
using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Protocol;
static (string host, int port) ParseEndpoint(string url)
{
foreach (var prefix in new[] { "tcp://", "tls://", "ws://" })
if (url.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
url = url[prefix.Length..];
var parts = url.TrimEnd('/').Split(':');
return (parts[0], parts.Length > 1 ? int.Parse(parts[1]) : 1883);
}
var url = Environment.GetEnvironmentVariable("KUBEMQ_MQTT_URL") ?? "tcp://localhost:1883";
var (host, port) = ParseEndpoint(url);
// Prefix "events/" selects the Events pattern; "/" becomes "." so
// "events/demo/x" maps to the KubeMQ channel "demo.x".
const string topic = "events/demo/x";
var factory = new MqttFactory();
using var client = factory.CreateMqttClient();
var options = new MqttClientOptionsBuilder()
.WithTcpServer(host, port)
.WithClientId("csharp-mqtt-events-pub")
.WithProtocolVersion(MQTTnet.Formatter.MqttProtocolVersion.V500)
.WithCleanSession(true)
.Build();
await client.ConnectAsync(options);
var pubResult = await client.PublishAsync(
new MqttApplicationMessageBuilder()
.WithTopic(topic)
.WithPayload(Encoding.UTF8.GetBytes("hello from MQTT"))
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
// MQTT 5.0 User Property round-trips as a KubeMQ Tag.
// Do NOT call WithRetainFlag(true) — a retained publish is silently dropped.
.WithUserProperty("sensor", "thermometer")
.Build());
if (pubResult.ReasonCode != MqttClientPublishReasonCode.Success)
throw new Exception($"PUBACK reason: {pubResult.ReasonCode}");
Console.WriteLine($"published to '{topic}' (channel demo.x)");
await client.DisconnectAsync();# The Ruby `mqtt` gem speaks MQTT 3.1.1 only (no User Properties, no v5 RPC).
require "mqtt"
require "uri"
uri = URI.parse(ENV.fetch("KUBEMQ_MQTT_URL", "tcp://localhost:1883"))
# Prefix "events/" selects the Events pattern; "/" becomes "." so
# "events/demo/x" maps to the KubeMQ channel "demo.x".
topic = "events/demo/x"
MQTT::Client.connect(
host: uri.host,
port: uri.port,
ssl: uri.scheme == "tls",
client_id: "ruby-mqtt-events-pub",
clean_session: true,
keep_alive: 30
) do |client|
# retain=false is mandatory — the broker silently drops retained publishes.
client.publish(topic, "hello from MQTT", false, 1)
puts "published to '#{topic}' (channel demo.x)"
enduse rumqttc::v5::mqttbytes::v5::PublishProperties;
use rumqttc::v5::mqttbytes::QoS;
use rumqttc::v5::{AsyncClient, MqttOptions};
use std::env;
use std::time::Duration;
fn parse_host_port(url: &str) -> (String, u16) {
let stripped = url
.trim_start_matches("tcp://")
.trim_start_matches("tls://")
.trim_start_matches("ws://");
let host_port = stripped.split('/').next().unwrap_or(stripped);
let mut parts = host_port.splitn(2, ':');
let host = parts.next().unwrap_or("localhost").to_string();
let port: u16 = parts.next().and_then(|p| p.parse().ok()).unwrap_or(1883);
(host, port)
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let url = env::var("KUBEMQ_MQTT_URL").unwrap_or_else(|_| "tcp://localhost:1883".to_string());
let (host, port) = parse_host_port(&url);
// Prefix "events/" selects the Events pattern; '/' becomes '.' so
// "events/demo/x" maps to the KubeMQ channel "demo.x".
let topic = "events/demo/x";
let mut opts = MqttOptions::new("rust-mqtt-events-pub", host, port);
opts.set_keep_alive(Duration::from_secs(30));
let (client, mut eventloop) = AsyncClient::new(opts, 10);
// Drive the event loop so the PUBLISH is flushed and the PUBACK is processed.
tokio::spawn(async move { while eventloop.poll().await.is_ok() {} });
// MQTT 5.0 User Properties round-trip as KubeMQ Tags.
let props = PublishProperties {
user_properties: vec![("sensor".to_string(), "thermometer".to_string())],
..Default::default()
};
// retain = false — a retained publish is silently dropped.
client
.publish_with_properties(topic, QoS::AtLeastOnce, false, b"hello from MQTT".as_ref(), props)
.await?;
println!("published to '{topic}' (channel demo.x)");
Ok(())
}Supported languages
The connector speaks standard MQTT, so any conformant client works. The examples pin one native MQTT client per language — there is no KubeMQ SDK and no proto bindings.
| Language | Client library | Protocol | Notes |
|---|---|---|---|
| Go | eclipse/paho.golang (paho.mqtt.golang for v3.1.1) | MQTT 5.0 | The connector's reference client. |
| Python | paho-mqtt ≥ 2.1 | MQTT 5.0 | Callback-API v2. |
| Java | Eclipse Paho mqttv5 | MQTT 5.0 | org.eclipse.paho.mqttv5.client. |
| JavaScript / TypeScript | mqtt.js ≥ 5.15 | MQTT 5.0 | Works over TCP, TLS, and WebSocket. |
| C# / .NET | MQTTnet ≥ 4.3.7 | MQTT 5.0 | Task-based async. |
| Ruby | mqtt gem ≥ 0.6 | MQTT 3.1.1 | v3.1.1 subset only — no User Properties, no RPC, no WebSocket. |
| Rust | rumqttc ≥ 0.24 | MQTT 5.0 | async/await on Tokio. |
The Ruby mqtt gem implements MQTT 3.1.1 only. The MQTT 5.0-only features — RPC
(Commands / Queries), shared-subscription queue consume, and User-Properties — are not
available from Ruby. Use a v5 client (Go, Python, Java, JavaScript, C#, Rust) for those
patterns. See Topic grammar.
Next steps
Getting started
Connect, publish, and subscribe end-to-end through the MQTT connector in minutes.
Configuration
The 17 connector settings, the CONNECTORSMQTT_ENABLE spelling, forced capabilities, and validation.
Events
Fire-and-forget pub/sub over MQTT — topic prefixes, wildcards, and User-Properties as Tags.
Topic grammar
The full topic grammar — prefixes, wildcards, $share queue consume, and $reply.
Was this page helpful?