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.RWMutex

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. 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.
  2. Ensure the client completes its QoS handshake on one connection before reconnecting; resend unacknowledged PUBLISH packets on the new connection instead.
  3. 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

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


AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02). Data as JSON: /api/errors/2bf5946b2dc6530b. Report an issue: GitHub.