The Paho MQTT client for Go can be surprisingly complex to set up for basic usage, often leading people to believe it’s more difficult than it actually is.

Let’s get a simple publisher and subscriber running. First, we need to install the Paho library:

go get github.com/eclipse/paho.mqtt.golang

Now, for the subscriber. This code will connect to a broker, subscribe to a topic, and print any messages it receives.

package main

import (
	"fmt"
	"log"
	"os"
	"time"

	mqtt "github.com/eclipse/paho.mqtt.golang"
)

func main() {
	// Broker details
	broker := "tcp://localhost:1883" // Replace with your MQTT broker address
	clientID := "go-subscriber-example"
	topic := "go/mqtt/topic"

	// Client options
	opts := mqtt.NewClientOptions().AddBroker(broker).SetClientID(clientID).SetCleanSession(true)

	// Set up a message handler
	opts.SetDefaultPublishHandler(messageHandler)

	// Set up connection and disconnection handlers
	opts.OnConnect = func(c mqtt.Client) {
		fmt.Println("Connected to broker!")
		// Subscribe to the topic
		if token := c.Subscribe(topic, 1, nil); token.Wait() && token.Error() != nil {
			log.Fatalf("Failed to subscribe: %v", token.Error())
		}
		fmt.Printf("Subscribed to topic: %s\n", topic)
	}
	opts.OnConnectionLost = func(c mqtt.Client, err error) {
		fmt.Printf("Connection lost: %v\n", err)
	}

	// Create and start the client
	client := mqtt.NewClient(opts)
	if token := client.Connect(); token.Wait() && token.Error() != nil {
		log.Fatalf("Failed to connect: %v", token.Error())
	}

	// Keep the subscriber running
	select {}
}

// messageHandler is the callback for received messages
var messageHandler mqtt.MessageHandler = func(client mqtt.Client, msg mqtt.Message) {
	fmt.Printf("Received message: %s from topic: %s\n", msg.Payload(), msg.Topic())
}

And here’s the publisher:

package main

import (
	"fmt"
	"log"
	"os"
	"time"

	mqtt "github.com/eclipse/paho.mqtt.golang"
)

func main() {
	// Broker details
	broker := "tcp://localhost:1883" // Replace with your MQTT broker address
	clientID := "go-publisher-example"
	topic := "go/mqtt/topic"

	// Client options
	opts := mqtt.NewClientOptions().AddBroker(broker).SetClientID(clientID).SetCleanSession(true)

	// Create and start the client
	client := mqtt.NewClient(opts)
	if token := client.Connect(); token.Wait() && token.Error() != nil {
		log.Fatalf("Failed to connect: %v", token.Error())
	}
	fmt.Println("Connected to broker!")

	// Publish a message every 2 seconds
	for i := 0; ; i++ {
		message := fmt.Sprintf("Hello from Go publisher! Message #%d", i)
		token := client.Publish(topic, 1, false, message)
		token.Wait() // Wait for the publish to complete
		if token.Error() != nil {
			log.Printf("Error publishing message: %v", token.Error())
		} else {
			fmt.Printf("Published: %s\n", message)
		}
		time.Sleep(2 * time.Second)
	}
}

To run this, you’ll need an MQTT broker. Mosquitto is a popular choice and can be installed easily. Start it with mosquitto. Then, in two separate terminals, run the subscriber and the publisher. You should see the publisher sending messages and the subscriber receiving them.

The core of the Paho client setup revolves around mqtt.NewClientOptions(). This struct is where you configure everything from broker addresses and client IDs to callback functions for various events. The AddBroker() method takes a string representing the broker’s URI, typically in the format tcp://host:port. SetClientID() assigns a unique identifier to your client, which the broker uses for tracking. SetCleanSession(true) tells the broker to discard all messages and subscriptions for this client upon disconnection, starting fresh next time.

The SetDefaultPublishHandler() is crucial for the subscriber. It registers a function that will be executed every time a message is received on any topic the client is subscribed to. This handler receives the client instance and an mqtt.Message object, which contains the topic, payload, and other message details.

OnConnect and OnConnectionLost are event handlers. OnConnect fires when the client successfully establishes a connection with the broker. This is the logical place to perform initial subscriptions, as shown in the subscriber example. OnConnectionLost is invoked if the connection to the broker is unexpectedly terminated.

The client.Connect() method initiates the connection, and token.Wait() is a blocking call that ensures the operation (connecting, subscribing, publishing) has completed before proceeding. It’s essential for handling potential errors. The mqtt.Token returned by most client operations is how you track their status.

A key detail often missed is how QoS (Quality of Service) levels affect message delivery and the client’s behavior. The subscriber example uses QoS 1 (c.Subscribe(topic, 1, nil)), meaning the publisher must acknowledge receipt of the message. The publisher example also uses QoS 1 (client.Publish(topic, 1, false, message)). The false parameter in Publish indicates that the message is not retained by the broker.

The select {} in the subscriber’s main function is a common Go idiom to create a goroutine that blocks indefinitely, preventing main from exiting and thus keeping the subscriber alive.

The most surprising aspect is how the mqtt.Token is used for all asynchronous operations. You don’t typically use channels for direct operation completion notification; instead, you wait on the token. This can feel a bit imperative compared to Go’s usual channel-based concurrency, but it’s how the Paho library is designed.

The next step is often managing persistent sessions or implementing more sophisticated error handling and reconnection strategies.

Want structured learning?

Take the full Mqtt course →