nats-io/nats-server · error
invalid MQTT session
Error message
invalid MQTT session
What it means
errMQTTInvalidSession is returned when an MQTT connection's session state is missing or does not match the connection performing the operation. At server/mqtt.go:5185 it fires if c.mqtt.sess is nil; at server/mqtt.go:5193 it fires if the locked session's owning connection sess.c is not the current connection. It guards QoS ack-processing paths (e.g. PUBREC handling) that require a valid, bound session.
Source
Thrown at server/mqtt.go:244
errMQTTTopicFilterCannotBeEmpty = errors.New("topic filter cannot be empty")
errMQTTMalformedVarInt = errors.New("malformed variable int")
errMQTTSecondConnectPacket = errors.New("received a second CONNECT packet")
errMQTTServerNameMustBeSet = errors.New("mqtt requires server name to be explicitly set")
errMQTTUserMixWithUsersNKeys = errors.New("mqtt authentication username not compatible with presence of users/nkeys")
errMQTTTokenMixWIthUsersNKeys = errors.New("mqtt authentication token not compatible with presence of users/nkeys")
errMQTTAckWaitMustBePositive = errors.New("ack wait must be a positive value")
errMQTTJSAPITimeoutMustBePositive = errors.New("JS API timeout must be a positive value")
errMQTTStandaloneNeedsJetStream = errors.New("mqtt requires JetStream to be enabled if running in standalone mode")
errMQTTConnFlagReserved = errors.New("connect flags reserved bit not set to 0")
errMQTTWillAndRetainFlag = errors.New("if Will flag is set to 0, Will Retain flag must be 0 too")
errMQTTPasswordFlagAndNoUser = errors.New("password flag set but username flag is not")
errMQTTCIDEmptyNeedsCleanFlag = errors.New("when client ID is empty, clean session flag must be set to 1")
errMQTTEmptyWillTopic = errors.New("empty Will topic not allowed")
errMQTTEmptyUsername = errors.New("empty user name not allowed")
errMQTTTopicIsEmpty = errors.New("topic cannot be empty")
errMQTTPacketIdentifierIsZero = errors.New("packet identifier cannot be 0")
errMQTTUnsupportedCharacters = errors.New("character not supported for MQTT topics")
errMQTTInvalidSession = errors.New("invalid MQTT session")
errMQTTInvalidRetainFlags = errors.New("invalid retained message flags")
errMQTTInvalidRetainedMessage = errors.New("invalid retained message")
errMQTTSessionCollision = errors.New("stored session does not match client ID")
errMQTTInvalidPublishLength = errors.New("invalid publish message, variable header exceeds remaining length")
errMQTTAckPipelineStopped = errors.New("QoS1 PUBACK pipeline has shut down while admitting a message, " +
"abandoning the wait for its JetStream ack; failing the connection, " +
"the client will re-send unacknowledged PUBLISH packets on reconnect")
)
type srvMQTT struct {
listener net.Listener
listenerErr error
authOverride bool
sessmgr mqttSessionManager
}
type mqttSessionManager struct {
mu sync.RWMutexView on GitHub (pinned to 3a66a489d2)
Solutions
- Use a unique client ID per connection (or rely on the server's takeover semantics) so an old connection does not process acks against a session it no longer owns.
- Ensure the client completes its QoS handshake on one connection before reconnecting; resend unacknowledged PUBLISH packets on the new connection instead.
- If you hit this in server code, check that the session is locked/valid (sess != nil && sess.c == c) before ack processing, and bail out cleanly as the code already does.
Example fix
// client: reuse of same client ID from two processes
clientId := "device-1" // both instances use it
// after: unique per instance
clientId := fmt.Sprintf("device-1-%s", instanceUUID) Defensive patterns
Strategy: try-catch
Validate before calling
func sessionValid(c *clientConn) bool {
if c.mqtt == nil || c.mqtt.sess == nil {
return false
}
return c.mqtt.sess.c == c
} Type guard
func (c *clientConn) hasValidSession() bool {
return c.mqtt != nil && c.mqtt.sess != nil && c.mqtt.sess.c == c
} Try / catch
if err := processAck(packet); err != nil {
if strings.Contains(err.Error(), "invalid MQTT session") {
// session taken over or closed; drop in-flight acks and reconnect
log.Info("session no longer valid; resending unacked publishes after reconnect")
reconnectAndResend()
return nil
}
return err
} Prevention
- Use unique client IDs per connection/process to avoid session takeover races
- Drain pending QoS acks before closing or reconnecting a client
- On reconnect, resend unacknowledged QoS>0 PUBLISH packets rather than assuming old session state
When it happens
Trigger: Processing a QoS ack (PUBREC/PUBACK path) after the connection's session has been torn down (sess == nil), or when the session object has been rebound to a different connection (sess.c != c), e.g. during session takeover by a new client with the same client ID.
Common situations: Client reconnects with the same client ID while old-connection acks are still in flight (session takeover); server shutting down or closing the session concurrently with ack processing; protocol races where the session is cleared before a queued ack is handled.
Related errors
- when client ID is empty, clean session flag must be set to 1
- stored session does not match client ID
- MQTT clients over websocket must connect to the Websocket po
- topic filter cannot be empty
- malformed variable int
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/2bf5946b2dc6530b.
Report an issue: GitHub.