Skip to content

Error Handling

Exception hierarchy

MQTTError
  ├── MQTTConnectError      # CONNACK refused (return_code attribute)
  ├── MQTTProtocolError     # malformed or unexpected packet
  ├── MQTTDisconnectedError # connection lost unexpectedly
  ├── MQTTTimeoutError      # PINGRESP or CONNACK timed out
  ├── MQTTSubscribeError    # one or more filters rejected by the broker
  ├── MQTTPublishError      # QoS 1/2 publish rejected by the broker
  └── MQTTInvalidTopicError # topic string failed MQTT validation

All exceptions are importable from zmqtt:

from zmqtt import (
    MQTTError,
    MQTTConnectError,
    MQTTProtocolError,
    MQTTDisconnectedError,
    MQTTTimeoutError,
    MQTTSubscribeError,
    MQTTPublishError,
    MQTTInvalidTopicError,
)

When each is raised

MQTTConnectError

Raised during __aenter__ when the broker refuses the connection. The return_code attribute holds the CONNACK return code:

from zmqtt import MQTTConnectError

try:
    async with create_client("localhost") as client:
        ...
except MQTTConnectError as e:
    print(f"Broker refused connection: code {e.return_code}")

For MQTT 5.0, e.properties contains the received ConnAckProperties. Convenience attributes expose the broker's diagnostics:

try:
    async with create_client("localhost", version="5.0") as client:
        ...
except MQTTConnectError as e:
    print(e.return_code, e.reason_string, e.server_reference)
    for key, value in e.user_properties:
        print(key, value)

Without properties (including MQTT 3.1.1), properties, reason_string, and server_reference are None, and user_properties is (). Repeated User Properties preserve their original order. The positional constructor MQTTConnectError(return_code) and exception text remain unchanged; properties can also be supplied with the optional keyword argument properties=.... A refused connection does not publish client.connection_info.

Common return codes (MQTT 3.1.1):

Code Meaning
1 Unacceptable protocol version
2 Client identifier rejected
3 Server unavailable
4 Bad username or password
5 Not authorised

MQTTSubscribeError

Raised when the broker rejects one or more topic filters in its SUBACK response. This commonly indicates an authorization failure. The failures attribute maps each rejected filter to its numeric reason code:

from zmqtt import MQTTSubscribeError

try:
    async with client.subscribe("private/#") as sub:
        ...
except MQTTSubscribeError as e:
    for topic_filter, reason_code in e.failures.items():
        print(f"{topic_filter!r} rejected: 0x{reason_code:02X}")

The same exception is raised by await sub.start() when using the manual subscription lifecycle.

MQTTPublishError

Raised on a version="5.0" connection when the broker rejects a QoS 1 or QoS 2 publish() — a PUBACK or PUBREC reason code of 0x80 or greater. A common cause is an authorization denial. The reason_code attribute holds the numeric code, reason_name the spec's name for it (None for a code zmqtt doesn't recognize), and reason_string the broker's optional Reason String property:

from zmqtt import MQTTPublishError, QoS

try:
    await client.publish("private/topic", b"payload", qos=QoS.AT_LEAST_ONCE)
except MQTTPublishError as e:
    print(f"Publish rejected: 0x{e.reason_code:02X} ({e.reason_name})")

For QoS 2, a rejected PUBREC ends the handshake immediately — no PUBREL is sent, since MQTT 5.0 only permits PUBREL after a PUBREC reason code below 0x80. In both cases the packet identifier is released and the connection remains usable for further operations.

Not raised for QoS 0, and not raised at all on version="3.1.1" connections — PUBACK and PUBREC carry no reason code in that protocol version, so a rejected publish there completes without error unless the broker instead closes the connection.

See PUBACK and PUBREC reason codes for more detail.

MQTTProtocolError

Raised when the broker sends a packet that violates the MQTT spec — wrong packet type in context, malformed header, etc. This usually indicates a broker bug or a mismatch between library version and broker behaviour.

MQTTDisconnectedError

Raised when an operation cannot continue because the connection was lost. If reconnection is enabled (the default), transient connection failures are normally handled automatically.

If reconnection is disabled (ReconnectConfig(enabled=False)), an in-progress or subsequent publish(), ping(), or subscription start may raise MQTTDisconnectedError. The exception is not injected asynchronously into unrelated application code.

MQTTTimeoutError

Raised by client.ping() when no PINGRESP arrives within the timeout:

try:
    rtt = await client.ping(timeout=5.0)
except MQTTTimeoutError:
    print("Broker not responding")

It is also raised by the connect handshake when the broker accepts the TCP connection but does not send a CONNACK within mqtt_connect_timeout seconds (default 30 s; see Connecting). When reconnection is enabled (the default), this is treated like any other connection failure: the client backs off and retries rather than surfacing the error.

See Manual Ping for the full ping() API.

MQTTInvalidTopicError

Raised when a topic string fails MQTT validation. The check happens eagerly — before any I/O — in publish(), subscribe(), and request().

publish() — topic name rules:

  • Must not be empty.
  • Must not contain + or # (wildcards are for filters only).
  • $ is only valid as the very first character.
from zmqtt import MQTTInvalidTopicError

try:
    await client.publish("sensors/+/temp", b"22.5")
except MQTTInvalidTopicError as e:
    print(e)  # Wildcards not allowed in publish topic: 'sensors/+/temp'

subscribe() — topic filter rules:

  • Must not be empty.
  • # must be the last character and, if not the only character, must be preceded by /.
  • + must occupy an entire level (e.g. a/+/b is valid; a/temp+/b is not).
  • $ is only valid as the very first character.
try:
    client.subscribe("sensors#")          # missing preceding '/'
    client.subscribe("sensors/temp+/data") # '+' not a full level
except MQTTInvalidTopicError as e:
    print(e)

request() — request and response topic rules:

The request topic and the response_topic property both follow the publish-topic rules. They are validated before zmqtt sends or subscribes to anything:

from zmqtt import MQTTInvalidTopicError

try:
    await client.request(
        "cmd/+",
        b"x",
    )
except MQTTInvalidTopicError as e:
    print(e)

The same exception is raised for an invalid custom response topic such as PublishProperties(response_topic="reply/+/bad").

See Request / Response for details.

Reconnection interaction

When ReconnectConfig(enabled=True) (the default), the client reconnects with exponential backoff. An active async for msg in sub loop keeps waiting and resumes after the subscription is restored. If all attempts fail, the optional on_connection_recovery_failed callback is invoked once and active subscription iterators raise the terminal connection error.

When reconnection is disabled, active subscription iterators raise MQTTDisconnectedError after connectivity is lost. A later operation that requires a live connection raises the same error.

Pending MQTT 5.0 requests have their own timeout and reconnect behaviour; see Request / Response.

See Reconnection for full details.


See also: Connecting · Manual Ping · Logging