MQTT Retain Handling Options: Make a New Subscriber’s Initial State Deliberate

Choose Retain Handling for the subscription’s initial stored values, Retain As Published for the RETAIN flag on later deliveries, and No Local for suppressing publications from the same ClientID. These are separate controls. Retain Handling two does not turn a subscription into a filter that rejects every future publication sent with RETAIN set.

This guide pins the OASIS MQTT 5.0 Standard, 7 March 2019, particularly sections 3.3.1.3, 3.8.3.1 and 3.8.4. It assumes a server supporting retained messages and nonshared subscriptions. An original option-state model was executed locally; no broker, client library or network behavior was tested.

Separate bootstrap from live forwarding

A stored retained message is the server’s retained value for one exact topic. Establishing a matching subscription can cause that value to be delivered. Later, a publisher can send a new publication on the same topic with RETAIN either clear or set. The subscription options address these two occasions differently.

Retain Handling New matching subscription Replace identical existing filter Later matching publications
0 Send stored retained values Send them again Not suppressed by this option
1 Send stored retained values Do not resend them Not suppressed by this option
2 No stored-value bootstrap No stored-value bootstrap Not suppressed by this option

The table assumes a matching retained value exists and delivery is otherwise permitted. “Existing” means an identical nonshared topic filter in the current session. A broad filter already covering the topic does not make a newly added, narrower filter the same subscription.

Subscription establishment selects retained bootstrap using Retain Handling: zero always, one only for a new filter, two never. Later publications independently pass the No Local decision and receive their RETAIN flag according to Retain As Published.
Initial retained delivery and later forwarding follow different decisions. Retain Handling does not suppress later retained publications.

Interpret the delivered RETAIN flag correctly

Retain As Published, abbreviated RAP, controls the flag on live forwarding. RAP zero clears RETAIN on those deliveries. RAP one preserves the publisher’s RETAIN setting. Stored retained messages sent when the subscription is established have RETAIN set regardless of RAP.

Consequently, RETAIN one is not a universal indication that a packet came from subscription bootstrap. With RAP one, a live publication can also carry it. If a consumer needs to distinguish initial state from later updates, its acceptance design must account for this ambiguity rather than treating the flag as a complete provenance label.

No Local, abbreviated NL, adds another independent decision. NL one prevents forwarding an application message to a connection whose ClientID equals the publishing connection’s ClientID. It is an identity comparison, not a comparison of usernames, machines or physical locations. For the initial tests, use a separate publisher identity so this control cannot hide the bootstrap under investigation.

A hypothetical dashboard and audit consumer

Suppose a hypothetical dashboard needs the last stored state when it first subscribes, but its reconnect helper routinely repeats subscription requests. Retain Handling one avoids another bootstrap when the identical filter already survives in the resumed session. It still supplies stored values when that filter is genuinely new.

An audit consumer interested only in future changes could choose Retain Handling two. That consumer must obtain any required starting state through a separately defined path. It will still receive subsequent matching publications whose publishers set RETAIN, subject to the other subscription rules.

For either client, RAP zero can make bootstrap packets recognizable by RETAIN one while ordinary live forwarding uses zero in this controlled, nonoverlapping setup. RAP one preserves the publisher’s flag instead. Choose based on what the consumer needs to know, not because “retain” appears in both option names.

Qualify replacement and session recovery

  1. In an isolated namespace, publish one identifiable retained fixture at QoS 1 from a separate test ClientID. Confirm acceptance and establish the baseline stored value.
  2. Create a new nonshared subscription with Retain Handling zero. Capture the matching stored-value delivery and its RETAIN flag.
  3. Repeat the identical filter in the same session with zero, then one, then two. Expect another bootstrap only for zero.
  4. Remove the filter through a completed unsubscribe, then create it with one. It is now new, so the stored value is eligible again.
  5. Repeat with a resumed session and with a genuinely new session, recording Session Present and the subscription ledger.
  6. Publish distinct live events with RETAIN zero and one under each RAP setting, then test same-ClientID versus separate-publisher forwarding under NL.

Do not infer session survival merely from reconnect success. Retain Handling one depends on whether the subscription actually exists. Keep the filter and session evidence beside the observed deliveries.

Acknowledge before claiming the test succeeded

Record the per-filter SUBACK reason and granted QoS. A multi-filter request can succeed for some filters and fail for others. Also prepare receive routing before sending SUBSCRIBE: the server may send matching PUBLISH packets before SUBACK. Discarding those early callbacks can make a correct bootstrap look missing.

Keep publishers quiescent during the initial retained matrix, then introduce clearly identified live events. Avoid overlapping filters in the baseline because they can create additional delivery explanations. A quiet observation interval alone does not prove an empty retained store; confirm permissions, subscription success and the known fixture.

Validate the option byte and record the boundary

The subscription byte assigns bits zero and one to requested maximum QoS, bit two to NL, bit three to RAP, and bits four and five to Retain Handling. Requested QoS three and Retain Handling three are invalid; the two upper reserved bits must be zero. NL one is also invalid for a shared subscription. Shared subscriptions have different bootstrap behavior and are outside this matrix.

The executed model checked all six new/existing Retain Handling combinations, sixteen live NL/RAP/publisher-identity/RETAIN combinations, and rejection of Retain Handling three. It did not encode packets or simulate a server. Use those cases as explicit expected decisions when qualifying the chosen implementation.

The deliverable is a subscription contract: required initial state, exact replacement behavior, live flag interpretation and self-publication policy. Once those are explicit, a missing or repeated initial value can be traced to a specific option, session transition or failed subscription rather than an ambiguous “retained messages” setting.

Similar Posts