Getting Mqtt5DisconnectException exception

com.hivemq.client.mqtt.mqtt5.exceptions.Mqtt5DisconnectException: QoS 2 PUBLISH must not be resent during the same connection.
fun publishToTopic(publishListener: WsMqttPublishListener, topic: String, payload: ByteArray?, isWait: Boolean) {

        logMqtt("MQTT-SEND >>>  publishToTopic >> topic = $topic", "d")

        if (isBuilderNotCreated(publishListener, arrayOf(topic), "PUBLISH")) {
            return
        }

        val mqtt5Publish = mqtt5Client!!
            .publishWith()
            .topic(topic)
            .payload(payload)
            .messageExpiryInterval(MQTT_MESSAGE_EXPIRY)
            .retain(false)
            .send()

        if (isWait) {
            mqtt5Publish.get()
        }

        mqtt5Publish.whenComplete { mqtt5PublishResult, throwable ->
            if (throwable == null) {
                val mPublish = mqtt5PublishResult.publish
                publishListener.onPublishSuccess(mPublish.topic.toString(), mPublish.payloadAsBytes)
            } else {
                publishListener.onPublishError(throwable)
            }
        }
    }

Hi @Aashima

Thank you for your question regarding the Mqtt5DisconnectException with the message “QoS 2 PUBLISH must not be resent during the same connection.”

According to the MQTT 5.0 specification, specifically section 4.4 on Message Delivery Retry, a PUBLISH packet with QoS 2 must not be resent during the same connection. It may only be resent when a session is present and the client reconnects with Clean Start set to false. This ensures the integrity of the QoS 2 flow and avoids duplicate message processing within an active connection.

In your current implementation, it appears that a resend of a QoS 2 message might be occurring within the same session, potentially due to retry logic triggered by a failed attempt. The MQTT client detects this and disconnects with the appropriate exception.

To resolve this, ensure that retries for QoS 2 messages do not occur within the same connection. Instead, allow the session recovery process to handle any unacknowledged packets after a proper disconnect and reconnect with session state preserved.

Below is a revised version of your publishToTopic function. The goal here is to ensure that duplicate sends are not initiated from the application side and that error handling does not trigger resend logic incompatible with the protocol:

override fun publishToTopic(publishListener: WsMqttPublishListener, topic: String, payload: ByteArray?, isWait: Boolean) {
    logMqtt("MQTT-SEND >>>  publishToTopic >> topic = $topic", "d")

    if (isBuilderNotCreated(publishListener, arrayOf(topic), "PUBLISH")) {
        return
    }

    try {
        val future = mqtt5Client!!
            .publishWith()
            .topic(topic)
            .payload(payload)
            .messageExpiryInterval(MQTT_MESSAGE_EXPIRY)
            .retain(false)
            .send()

        if (isWait) {
            future.get()
        }

        future.whenComplete { result, throwable ->
            if (throwable == null) {
                val published = result.publish
                publishListener.onPublishSuccess(published.topic.toString(), published.payloadAsBytes)
            } else {
                // Do not retry here for QoS 2
                publishListener.onPublishError(throwable)
            }
        }
    } catch (ex: Mqtt5DisconnectException) {
        // Log or handle the disconnect appropriately
        publishListener.onPublishError(ex)
    } catch (ex: Exception) {
        publishListener.onPublishError(ex)
    }
}

To summarize, avoid initiating any resend of QoS 2 messages in the same session, and allow the client library and broker to manage retransmissions as per MQTT protocol rules. If the message is critical, consider handling session persistence and reconnect behavior carefully to trigger appropriate message delivery retry after a disconnect.

Let me know if further clarification is needed.

Best,
Dasha from The HiveMQ Team