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 MqttDefaultFilePersistence — including 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
- Connect with
cleanStart=false, automaticReconnect=true, MqttDefaultFilePersistence, keepAliveInterval=5
- Publish QoS 1 or QoS 2 messages with
topicAlias set in MqttProperties
- Simulate broker unavailability long enough to trigger a keepAlive timeout
- Restore broker availability
- 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:
- Detect the presence of a
topicAlias in the message properties
- Strip the alias
- 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
Version
1.2.5
Related Issues
Description
When
automaticReconnect=true,cleanStart=false, andMqttDefaultFilePersistenceis 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
topicAliasin itsMqttPropertiesis in-flight at the time of disconnection, Paho serializes that message toMqttDefaultFilePersistence— including thetopicAliasvalue andtopic=null. On reconnect, Paho re-delivers the message from disk with the staletopicAlias=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:
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
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.javacontinues to have unresolved defects beyond what #527 addressed.Steps to Reproduce
cleanStart=false,automaticReconnect=true,MqttDefaultFilePersistence,keepAliveInterval=5topicAliasset inMqttPropertiesDebug Log
Step 1 — KeepAlive timeout: PINGREQ sent, no PINGRESP received
Step 2 — Message in-flight at time of disconnect: persisted with topicAlias=1, topic=null
Step 3 — Reconnect attempts with exponential backoff (broker down)
Step 4 — Reconnect succeeds, CONNACK sessionPresent=false, stale alias re-delivered immediately
Step 5 — Broker disconnects with RC 148, infinite loop begins
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 carriestopicAlias=1, topic=nulland 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
MqttDefaultFilePersistenceon reconnect, Paho should:topicAliasin the message propertiesCommsTokenStore)This must happen regardless of
sessionPresentsince topic alias mappings are connection-scoped per the MQTT v5 specification and are never valid after a new CONNECT.Workaround
cleanStart=true— prevents persisted in-flight retry, but sacrifices session continuitytopicAliason messages — eliminates the trigger but forgoes the bandwidth optimizationApplication Debug Logs:
AppDebugLogs.log