Skip to main content

Subscribing to Topics

In MQTT, "subscribe" is an operation that allows an MQTT client to request to receive messages published to specific topics from an MQTT broker.

Simple

Use the SubscribeAsync method to subscribe to the desired topic by providing the topic string as a parameter.

await client.SubscribeAsync("instrument/x9284/boston").ConfigureAwait(false);

Optionally, you can specify the desired quality of service (QoS) level for the subscription. By default, the QoS level is set to QualityOfService.AtMostOnceDelivery.

using HiveMQtt.MQTT5.Types; // For QualityOfService enum

string topic = "my/topic";
QualityOfService qos = QualityOfService.AtLeastOnceDelivery;

await client.SubscribeAsync(topic, qos).ConfigureAwait(false);
tip

Make sure to set your message handlers before subscribing. See this section below for more details.

With Options

The SubscribeOptionsBuilder class provides a convenient way to construct subscription options for MQTT subscriptions. It allows you to configure various aspects of the subscription(s), including the topic filter, quality of service (QoS) level, user properties, and more.

To use the SubscribeOptionsBuilder:

Create an instance of the SubscribeOptionsBuilder class.

var builder = new SubscribeOptionsBuilder();

Use the WithSubscription method to specify the topic filter and QoS level for the subscription. This method can be called multiple times to create multiple subscriptions at once.

builder.WithSubscription("topic1", QualityOfService.AtLeastOnceDelivery)
.WithSubscription("topic2", QualityOfService.ExactlyOnceDelivery);

Optionally, you can use the WithUserProperties method to add custom user properties to the subscription. User properties are key-value pairs that provide additional metadata or application-specific information.

var userProperties = new Dictionary<string, string>
{
{ "property1", "value1" },
{ "property2", "value2" }
};

builder.WithUserProperties(userProperties);

There also exists a singular WithUserProperty if you just need to send one key-value pair:

builder.WithUserProperty("property1", "value1")

Call the Build method to create the SubscribeOptions object.

var options = builder.Build();

Use the created SubscribeOptions object when subscribing to MQTT topics using the MQTT client library.

await client.SubscribeAsync(options);

By using the SubscribeOptionsBuilder, you can easily configure multiple subscriptions with different topic filters and QoS levels. Additionally, you have the flexibility to include custom user properties to provide additional information or metadata for the subscriptions.

SubscribeOptionsBuilder Reference

To illustrate each and every possible call with SubscribeOptionsBuilder, see the following example:

using HiveMQtt.MQTT5.Types;

var options = new SubscribeOptionsBuilder()
.WithSubscription(
"topic1", // Topic
QualityOfService.ExactlyOnceDelivery, // Quality of Service Level
true, // NoLocal
true, // RetainAsPublished
RetainHandling.SendAtSubscribe) // RetainHandling
.WithUserProperty("property1", "value1")
.WithUserProperties(
new Dictionary<string, string> {
{ "property1", "value1" }, { "property2", "value2" } })
.Build();

In WithSubscription, the first two arguments are required. The additional optional parameters are defined as:

  • NoLocal: The NoLocal option, when set to true, indicates that the subscriber does not want to receive messages published by itself. This option is useful in scenarios where a client is publishing and subscribing to the same topic. By setting NoLocal to true, the client can avoid receiving its own published messages.

  • RetainAsPublished: The RetainAsPublished option, when set to false, indicates that the broker should not send retained messages to the subscriber when it first subscribes to a topic. Retained messages are those that are stored by the broker and sent to new subscribers upon subscription. By setting RetainAsPublished to false, the subscriber will not receive any retained messages for that topic.

  • Retain handling: Retain handling refers to the behavior of the broker when it receives a subscription request for a topic that has retained messages. In MQTT 5, there are three options for retain handling:

    • RetainHandling.SendAtSubscribe: The broker sends any retained messages for the topic to the subscriber immediately upon subscription.
    • RetainHandling.SendAtSubscribeIfNewSubscription: The broker sends retained messages to new subscribers only if there are no existing subscriptions for that topic.
    • RetainHandling.DoNotSendAtSubscribe: The broker does not send any retained messages to the subscriber upon subscription.

These options provide flexibility and control over the behavior of the subscription process in MQTT 5, allowing subscribers to customize their experience based on their specific requirements.

Important Tip: Prioritize Setting Your Message Handlers

In MQTT communication, the message handler is responsible for processing incoming messages from the broker. It's crucial to set up your message handler before establishing a connection to the MQTT broker.

Why is this order important? Once a connection is established, the broker may start sending messages immediately, especially if there are retained messages for the topics you're subscribing to. If the message handler is not set up in advance, these incoming messages might not be processed, leading to potential data loss or unexpected behavior.

// Message Handler
client.OnMessageReceived += (sender, args) =>
{
Console.WriteLine($"Message Received: {args.PublishMessage.PayloadAsString}");
};

await client.ConnectAsync();

or alternatively:

private static void MessageHandler(object? sender, OnMessageReceivedEventArgs eventArgs)
{
Console.WriteLine($"Message Received: {eventArgs.PublishMessage.PayloadAsString}");
}

client.OnMessageReceived += MessageHandler;
await client.ConnectAsync();

In this example, the message handler is defined as a lambda function that writes the received message to the console. Only after the message handler is set up do we connect to the broker using the ConnectAsync method.

Remember, prioritizing the setup of your message handler ensures that your application is ready to process incoming messages as soon as the connection to the broker is established.

Subscribe: Multiple Topics At Once

using HiveMQtt.Client.Options;
using HiveMQtt.Client.Results;

var options = new SubscribeOptions();
options.TopicFilters.Add(new TopicFilter { Topic = "foo/boston", QoS = QualityOfService.AtLeastOnceDelivery });
options.TopicFilters.Add(new TopicFilter { Topic = "bar/landshut", QoS = QualityOfService.AtMostOnceDelivery });

var result = await client.SubscribeAsync(options);
  • result.Subscriptions contains the list of subscriptions made with this call
  • client.Subscriptions is updated with complete list of subscriptions made up to this point
  • each Subscription object has a resulting ReasonCode that represents the Subscribe result in result.Subscriptions[0].ReasonCode

Using SubscribeOptionsBuilder

var subscribeOptions = new SubscribeOptionsBuilder()
.WithSubscription("my/topic1", MQTT5.Types.QualityOfService.AtLeastOnceDelivery)
.WithSubscription("my/topic/2", MQTT5.Types.QualityOfService.AtLeastOnceDelivery, true, true, MQTT5.Types.RetainHandling.SendAtSubscribe)
.WithUserProperty("Client-Geo", "38.115662, 13.361470")
.Build();

var subResult = await subClient.SubscribeAsync(subscribeOptions).ConfigureAwait(false);

Per Subscription Callbacks

Introduction

The SubscribeOptionsBuilder class in the HiveMQtt client library provides a convenient way to configure subscription options for MQTT subscriptions. One of the key features of the SubscribeOptionsBuilder is the ability to specify per subscription callbacks using the WithSubscription method. This allows you to define custom event handlers that will be invoked when messages are received for specific topics.

The Problem

In MQTT communication, it is common to have different subscriptions for different topics, each requiring specific handling or processing of the received messages. The challenge is to associate a specific callback or event handler with each subscription, so that the appropriate logic can be executed when messages are received for those topics.

Per Subscription Callbacks with `WithSubscription``

The WithSubscription method of the SubscribeOptionsBuilder class provides a solution to this problem. It allows you to specify a topic filter and an event handler that will be associated with that topic filter. The event handler will be invoked whenever a message is received for the subscribed topic.

The signature of the WithSubscription method is as follows:

public SubscribeOptionsBuilder WithSubscription(TopicFilter topicFilter, EventHandler<OnMessageReceivedEventArgs>? handler = null)

Here's an example of how you might use the WithSubscription method to set up a per subscription callback:

var builder = new SubscribeOptionsBuilder();
var options = builder.WithSubscription(
new TopicFilter("test/topic", QualityOfService.AtLeastOnceDelivery),
(sender, e) =>
{
Console.WriteLine($"Message received on topic {e.PublishMessage.Topic}: {e.PublishMessage.PayloadAsString}");
})
.Build();

In this example, we first create an instance of SubscribeOptionsBuilder. Then we call the WithSubscription method to add a subscription with a topic filter and an event handler. The event handler is a lambda function that writes a message to the console whenever a message is received on the subscribed topic. Finally, we call the Build method to create the SubscribeOptions.

Alternatively the message handler can be independently defined:

private static void MessageHandler(object? sender, OnMessageReceivedEventArgs eventArgs)
{
Console.WriteLine($"Message Received: {eventArgs.PublishMessage.PayloadAsString}");
}

var builder = new SubscribeOptionsBuilder();
var options = builder.WithSubscription(
new TopicFilter("test/topic", QualityOfService.AtLeastOnceDelivery),
MessageHandler)
.Build();

Overlapping Subscriptions

The Problem

When you subscribe to overlapping topic patterns, a single published message may match multiple subscriptions. For example:

await client.SubscribeAsync("sensors/#");           // Matches: sensors/temperature/livingroom
await client.SubscribeAsync("sensors/+/temperature"); // Matches: sensors/livingroom/temperature

A message published to sensors/livingroom/temperature matches both subscriptions.

Depending on your MQTT broker, this can result in:

  • Single delivery: One PUBLISH packet sent by the broker
  • Multiple deliveries: Multiple PUBLISH packets (one per matching subscription)

The OverlappingSubscriptionBehavior setting controls how the client handles this.

Behavior Options

The HiveMQClientOptions.OverlappingSubscriptionBehavior property controls this behavior:

OptionDescriptionWhen to Use
FireAllMatchingHandlers (default)Fires a handler for each matching subscriptionBackward compatibility, different logic per subscription
FireFirstMatchingHandlerFires only the first matching subscription handlerSingle message processing, simpler handling

Option 1: FireAllMatchingHandlers (Default)

Behavior: The OnMessageReceived event fires once for each matching subscription that has a handler registered.

Example:

var options = new HiveMQClientOptionsBuilder()
.WithOverlappingSubscriptionBehavior(OverlappingSubscriptionBehavior.FireAllMatchingHandlers) // Default
.Build();

var client = new HiveMQClient(options);

var opts1 = new SubscribeOptions();
opts1.TopicFilters.Add(new TopicFilter("sensors/#", QualityOfService.AtLeastOnceDelivery));
opts1.Handlers["sensors/#"] = (s, e) => Console.WriteLine("Handler 1: Wildcard match");
await client.SubscribeAsync(opts1);

var opts2 = new SubscribeOptions();
opts2.TopicFilters.Add(new TopicFilter("sensors/+/temperature", QualityOfService.AtLeastOnceDelivery));
opts2.Handlers["sensors/+/temperature"] = (s, e) => Console.WriteLine("Handler 2: Specific match");
await client.SubscribeAsync(opts2);

// Publish to sensors/livingroom/temperature
// Output:
// Handler 1: Wildcard match
// Handler 2: Specific match
note

Only subscriptions with handlers fire events. The global OnMessageReceived event always fires separately.

Behavior: The OnMessageReceived event fires only once for the first matching subscription.

Example:

var options = new HiveMQClientOptionsBuilder()
.WithOverlappingSubscriptionBehavior(OverlappingSubscriptionBehavior.FireFirstMatchingHandler)
.Build();

var client = new HiveMQClient(options);

var opts1 = new SubscribeOptions();
opts1.TopicFilters.Add(new TopicFilter("sensors/#", QualityOfService.AtLeastOnceDelivery));
opts1.Handlers["sensors/#"] = (s, e) => Console.WriteLine("Handler 1: Wildcard match");
await client.SubscribeAsync(opts1); // First subscription

var opts2 = new SubscribeOptions();
opts2.TopicFilters.Add(new TopicFilter("sensors/+/temperature", QualityOfService.AtLeastOnceDelivery));
opts2.Handlers["sensors/+/temperature"] = (s, e) => Console.WriteLine("Handler 2: Specific match");
await client.SubscribeAsync(opts2); // Second subscription

// Publish to sensors/livingroom/temperature
// Output:
// Handler 1: Wildcard match
// (Handler 2 does NOT fire because it's not the first match)
tip

"First" means first in subscription order (the order you called SubscribeAsync). The global OnMessageReceived event always fires regardless of this setting.

Edge Cases

First Subscription Has No Handler

If the first matching subscription has no handler, but a later one does:

await client.SubscribeAsync("sensors/#");  // No per-subscription handler

var opts = new SubscribeOptions();
opts.TopicFilters.Add(new TopicFilter("sensors/+/temperature", QualityOfService.AtLeastOnceDelivery));
opts.Handlers["sensors/+/temperature"] = MyHandler;
await client.SubscribeAsync(opts);

// Publish to sensors/livingroom/temperature
// With FireFirstMatchingHandler:
// - NO per-subscription handler fires (sensors/# was first but has no handler)
// - Global OnMessageReceived still fires

Recommendation: Either register handlers on all overlapping subscriptions, use the global OnMessageReceived event, or ensure your most specific subscription is added first.

Mixed Global and Per-Subscription Handlers

The global OnMessageReceived event is not affected by the behavior setting:

var options = new HiveMQClientOptionsBuilder()
.WithOverlappingSubscriptionBehavior(OverlappingSubscriptionBehavior.FireFirstMatchingHandler)
.Build();

var client = new HiveMQClient(options);

// Global handler
client.OnMessageReceived += (s, e) => Console.WriteLine("Global handler");

// Per-subscription handler
var opts = new SubscribeOptions();
opts.TopicFilters.Add(new TopicFilter("sensors/#", QualityOfService.AtLeastOnceDelivery));
opts.Handlers["sensors/#"] = (s, e) => Console.WriteLine("Per-subscription handler");
await client.SubscribeAsync(opts);

// Publish to sensors/temperature
// Output:
// Global handler
// Per-subscription handler
// (Both fire - global is always independent)

Quick Reference Table

ScenarioFireAllMatchingHandlersFireFirstMatchingHandler
3 overlapping subs, all with handlers3 handler calls1 handler call (first only)
3 overlapping subs, only 2nd has handler1 handler call (2nd only)0 handler calls (1st has none)
Global + 2 per-sub handlersGlobal + 2 handlersGlobal + 1 handler (first only)
Global only (no per-sub handlers)Global fires onceGlobal fires once

Migration Guide

For New Users

We recommend using FireFirstMatchingHandler for new applications:

var options = new HiveMQClientOptionsBuilder()
.WithOverlappingSubscriptionBehavior(OverlappingSubscriptionBehavior.FireFirstMatchingHandler)
.Build();

var client = new HiveMQClient(options);

For Existing Users

No changes required - the default remains FireAllMatchingHandlers for backward compatibility.

See Also