nats-io/nats-server · error

create QoS2 incoming messages stream for account %q: %v

Error message

create QoS2 incoming messages stream for account %q: %v

What it means

For MQTT QoS2, each in-flight message gets its own subject in the QoS2 incoming-messages stream, configured with DiscardNew, MaxMsgsPer=1 and DiscardNewPer. If creating this stream fails for reasons other than it already existing (JSStreamNameExistErr), the error is wrapped with the account name and returned, aborting the MQTT session setup.

Source

Thrown at server/mqtt.go:1466

	if si, err := lookupStream(mqttQoS2IncomingMsgsStreamName, "QoS2 incoming messages"); err != nil {
		return nil, err
	} else if si == nil {
		// Create the stream for the incoming QoS2 messages that have not been
		// PUBREL-ed by the sender. Subject is
		// "$MQTT.qos2.<session>.<PI>", the .PI is to achieve exactly
		// once for each PI.
		cfg := &StreamConfig{
			Name:          mqttQoS2IncomingMsgsStreamName,
			Subjects:      []string{mqttQoS2IncomingMsgsStreamSubjectPrefix + ">"},
			Storage:       FileStorage,
			Retention:     LimitsPolicy,
			Discard:       DiscardNew,
			MaxMsgsPer:    1,
			DiscardNewPer: true,
			Replicas:      replicas,
		}
		if _, _, err := jsa.createStream(cfg); isErrorOtherThan(err, JSStreamNameExistErr) {
			return nil, fmt.Errorf("create QoS2 incoming messages stream for account %q: %v", accName, err)
		}
	}

	if si, err := lookupStream(mqttOutStreamName, "QoS2 outgoing PUBREL"); err != nil {
		return nil, err
	} else if si == nil {
		// Create the stream for the incoming QoS2 messages that have not been
		// PUBREL-ed by the sender. NATS messages are submitted as
		// "$MQTT.pubrel.<session hash>"
		cfg := &StreamConfig{
			Name:      mqttOutStreamName,
			Subjects:  []string{mqttOutSubjectPrefix + ">"},
			Storage:   FileStorage,
			Retention: InterestPolicy,
			Replicas:  replicas,
		}
		if _, _, err := jsa.createStream(cfg); isErrorOtherThan(err, JSStreamNameExistErr) {
			return nil, fmt.Errorf("create QoS2 outgoing PUBREL stream for account %q: %v", accName, err)

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Check the wrapped inner error for the exact JS cause.
  2. Ensure the NATS server/JS version supports DiscardNewPer (upgrade if running an older JS tier).
  3. Raise the account's stream count/storage limits.
  4. Confirm JetStream health and permissions for the account.
Defensive patterns

Strategy: retry

Validate before calling

// Verify server/JS version supports DiscardNewPer before using QoS2 MQTT
if (serverInfo.proto < MIN_PROTO_DISCARD_NEW_PER) {
  throw new Error('upgrade NATS server: QoS2 stream config requires newer JetStream');
}

Try / catch

try {
  await mqttConnect();
} catch (e) {
  if (/create QoS2 incoming messages stream/.test(e.message)) {
    if (/discard|config/.test(e.message)) upgradeServer();
    else raiseAccountLimits();
    return mqttConnect();
  }
  throw e;
}

Prevention

When it happens

Trigger: jsa.createStream(cfg) for the '<account>-mqtt-q2' stream returns an error other than JSStreamNameExistErr — e.g. account JS limits reject the per-subject discard configuration, storage limits hit, or JS API/permissions errors.

Common situations: Older/limited JS tiers that don't support DiscardNewPer; account max-streams quota reached; storage exhausted; JS temporarily unavailable.

Related errors


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