Skip to content

[BUG] Persisted alias-only QoS 1/2 PUBLISH causes RC 148 (reconnect) and RC 130 (restart) infinite disconnect loop — gap in #527 fix #1104

Description

@vpeddada-tibco

Version

1.2.5

Related Issues

Description

When automaticReconnect=true, cleanStart=false, and MqttDefaultFilePersistence is used, an automatic reconnect after a keepAlive timeout causes an immediate broker-initiated DISCONNECT with reason code 148 (Topic Alias Invalid), triggering an infinite reconnect loop. This affects QoS 1 and QoS 2 messages alike, as both are persisted to disk when in-flight.

Root Cause

Issue #527 was closed with a fix that reset the in-memory outgoing topic alias table on reconnect. However, that fix is incomplete for the following scenario:

When a QoS 1 or QoS 2 message carrying topicAlias in its MqttProperties is in-flight at the time of disconnection, Paho serializes that message to MqttDefaultFilePersistenceincluding the topicAlias value and topic=null. On reconnect, Paho re-delivers the message from disk with the stale topicAlias=N, topic=null. The new broker session has no alias mapping established, so it correctly rejects it per MQTT v5 spec §3.3.2.3.4 and disconnects with RC 148, which immediately triggers another reconnect — repeating infinitely.

Per the MQTT v5 specification:

"A Topic Alias is an integer value that is used to identify the Topic instead of using the Topic Name. Topic Aliases are scoped to a Network Connection."

Topic alias mappings are strictly per-connection, not per-session. They must be re-established from scratch on every new CONNECT regardless of sessionPresent. Paho's fix for #527 addresses the in-memory alias table but leaves persisted in-flight messages carrying stale aliases untouched.

Evidence of Gap in #527 Fix

Scenario Covered by #527 fix This bug
New publish after reconnect uses stale alias from in-memory table Fixed N/A
Persisted in-flight message (QoS 1/2) re-delivered from disk with stale alias Not fixed This issue

Additionally, the still-open issue #866 ("fix topic alias Always empty", filed March 2021 against 1.2.5 and 1.2.6-SNAPSHOT) demonstrates that topic alias handling in ClientState.java continues to have unresolved defects beyond what #527 addressed.

Steps to Reproduce

  1. Connect with cleanStart=false, automaticReconnect=true, MqttDefaultFilePersistence, keepAliveInterval=5
  2. Publish QoS 1 or QoS 2 messages with topicAlias set in MqttProperties
  3. Simulate broker unavailability long enough to trigger a keepAlive timeout
  4. Restore broker availability
  5. Observe: Paho reconnects successfully (CONNACK returnCode=0) but immediately receives broker DISCONNECT RC 148, triggering another reconnect cycle

Debug Log

Step 1 — KeepAlive timeout: PINGREQ sent, no PINGRESP received

2026-06-18 13:54:18.679 DEBUG [MQTT Ping: Demo5] ClientState - ping needed. keepAlive=5,000,000,000 lastOutboundActivity=2,303,715,409,191,300 lastInboundActivity=2,303,715,399,737,400
2026-06-18 13:54:18.680 DEBUG [MQTT Snd: Demo5]  CommsSender  - network send key=Ping msg=PINGREQ
2026-06-18 13:54:18.681 DEBUG [MQTT Snd: Demo5]  ClientState  - ping sent. pingOutstanding: 1
-- No PINGRESP received. CommsReceiver polls for 5 seconds with no data --
2026-06-18 13:54:23.691 ERROR [MQTT Ping: Demo5] ClientState  - Timed out as no activity,
    keepAlive=5,000,000,000
    lastOutboundActivity=2,303,725,398,704,300
    lastInboundActivity=2,303,715,399,737,400   <-- 10s gap, two keepAlive periods
    lastPing=2,303,720,413,051,800
org.eclipse.paho.mqttv5.common.MqttException: Timed out waiting for a response from the server
    at org.eclipse.paho.mqttv5.client.internal.ClientState.checkForActivity(ClientState.java:736)
    at org.eclipse.paho.mqttv5.client.TimerPingSender$PingTask.run(TimerPingSender.java:105)

Step 2 — Message in-flight at time of disconnect: persisted with topicAlias=1, topic=null

2026-06-18 13:54:23.657 DEBUG [pool-16-thread-1] ClientState - pending publish key=6 qos=1
    message=MqttPublish [qos=1, messageId=6, retained=false, duplicate=false,
    topic=null,
    payload=[utf8=Hello MQTT],
    properties=[topicAlias=1, userProperties=[...], contentType=text/plain,
                messageExpiryInterval=23423423, correlationData=[...]]]
2026-06-18 13:54:23.707 DEBUG [MQTT Ping: Demo5] ClientState    - Clearing Connection State (Topic Aliases)
2026-06-18 13:54:23.712 DEBUG [MQTT Ping: Demo5] MqttAsyncClient - Start reconnect timer for client: Demo5, delay: 1,000

Step 3 — Reconnect attempts with exponential backoff (broker down)

2026-06-18 13:54:24.713 DEBUG [MQTT Reconnect: Demo5] MqttAsyncClient - Triggering Automatic Reconnect attempt. delay=1000
2026-06-18 13:54:24.814 DEBUG [MQTT Rec:     Demo5] MqttAsyncClient - Automatic Reconnect failed, rescheduling. delay=2000
2026-06-18 13:54:26.817 DEBUG [MQTT Reconnect: Demo5] MqttAsyncClient - Triggering Automatic Reconnect attempt. delay=2000
2026-06-18 13:54:27.886 DEBUG [MQTT Rec:     Demo5] MqttAsyncClient - Automatic Reconnect failed, rescheduling. delay=4000
2026-06-18 13:54:31.889 DEBUG [MQTT Reconnect: Demo5] MqttAsyncClient - Triggering Automatic Reconnect attempt. delay=4000
2026-06-18 13:54:34.029 DEBUG [MQTT Rec:     Demo5] MqttAsyncClient - Automatic Reconnect failed, rescheduling. delay=8000
2026-06-18 13:54:42.031 DEBUG [MQTT Reconnect: Demo5] MqttAsyncClient - Triggering Automatic Reconnect attempt. delay=8000

Step 4 — Reconnect succeeds, CONNACK sessionPresent=false, stale alias re-delivered immediately

2026-06-18 13:54:42.368 DEBUG [MQTT Rec: Demo5] MqttInputStream - Received MqttConnAck
    [returnCode=0, sessionPresent=false,
     properties=[receiveMaximum=10, maximumPacketSize=10048576, topicAliasMaximum=5]]

-- Paho re-delivers persisted in-flight message from disk BEFORE connectComplete fires --

2026-06-18 13:54:42.370 DEBUG [MQTT Snd: Demo5] CommsSender - network send key=6
    msg=MqttPublish [qos=1, messageId=6, retained=false, duplicate=true,
    topic=null,               <-- topic is null, alias-only
    properties=[topicAlias=1, <-- stale alias from previous connection
                userProperties=[...], contentType=text/plain,
                messageExpiryInterval=23423423]]

2026-06-18 13:54:42.370 DEBUG [MQTT Call: Demo5] MqttAsyncClient - Automatic Reconnect Successful: Demo5

Step 5 — Broker disconnects with RC 148, infinite loop begins

2026-06-18 13:54:42.373 DEBUG [MQTT Rec: Demo5] MqttInputStream - Received MqttDisconnect
    [returnCode=148,
     properties=[reasonString=Topic alias in PUBLISH could not be mapped.]]

2026-06-18 13:54:42.376 DEBUG [MQTT Rec: Demo5] ClientState - reason:
    The Server Disconnected the client. Disconnect RC: 148
    Disconnect Reason: Topic alias in PUBLISH could not be mapped. (32204)
org.eclipse.paho.mqttv5.common.MqttException: The Server Disconnected the client.
    Disconnect RC: 148 Disconnect Reason: Topic alias in PUBLISH could not be mapped.
    at org.eclipse.paho.mqttv5.client.internal.CommsReceiver.run(CommsReceiver.java:162)

2026-06-18 13:54:42.377 DEBUG [MQTT Rec: Demo5] ClientState    - Clearing Connection State (Topic Aliases)
2026-06-18 13:54:42.380 DEBUG [MQTT Rec: Demo5] MqttAsyncClient - Start reconnect timer for client: Demo5, delay: 1,000

-- Cycle repeats: reconnect -> CONNACK -> re-deliver stale alias message -> RC 148 -> reconnect --

Key observation: The log line Clearing Connection State (Topic Aliases) appears on every disconnect — confirming the in-memory table IS cleared by the #527 fix. But the persisted message on disk still carries topicAlias=1, topic=null and is re-sent unchanged on the next reconnect, proving the fix is incomplete for the persistence path.

Expected Behavior

Before re-delivering any persisted in-flight message from MqttDefaultFilePersistence on reconnect, Paho should:

  1. Detect the presence of a topicAlias in the message properties
  2. Strip the alias
  3. Restore the full topic name from the token store (topic is tracked separately in CommsTokenStore)

This must happen regardless of sessionPresent since topic alias mappings are connection-scoped per the MQTT v5 specification and are never valid after a new CONNECT.

Workaround

  • Use cleanStart=true — prevents persisted in-flight retry, but sacrifices session continuity
  • Avoid setting topicAlias on messages — eliminates the trigger but forgoes the bandwidth optimization
  • Neither is acceptable in production scenarios requiring both reliable delivery and efficient topic alias use

Application Debug Logs:

AppDebugLogs.log

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions