nats-io/nats-server · error · ApiError (JSStreamInvalidConfigF)

10052

10052

Error message

counter stream cannot use message TTLs

What it means

Counter streams (AllowMsgCounter=true) cannot also enable per-message TTLs (AllowMsgTTL): message-level expiration interferes with counter state consistency, so server/stream.go validation rejects the combination. Returned as a JSStreamInvalidConfigError. The associated code for these newer counter validation errors is 10052 (JSStreamInvalidConfig).

Source

Thrown at server/stream.go:1999

	if lim.Duplicates > 0 && cfg.Duplicates > lim.Duplicates {
		return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("duplicates window can not be larger then server limit of %v",
			lim.Duplicates.String()))
	}
	if cfg.Duplicates > 0 && cfg.Duplicates < 100*time.Millisecond {
		return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("duplicates window needs to be >= 100ms"))
	}

	if cfg.DenyPurge && cfg.AllowRollup {
		return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("roll-ups require the purge permission"))
	}

	// Counter is not compatible with some settings.
	if cfg.AllowMsgCounter {
		if cfg.Discard == DiscardNew {
			return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("counter stream cannot use discard new"))
		}
		if cfg.AllowMsgTTL {
			return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("counter stream cannot use message TTLs"))
		}
		if cfg.AllowMsgSchedules {
			return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("counter stream cannot use message schedules"))
		}
		if cfg.Retention != LimitsPolicy {
			return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("counter stream can only use limits retention"))
		}
	}

	// Check for new discard new per subject, we require the discard policy to also be new.
	if cfg.DiscardNewPer {
		if cfg.Discard != DiscardNew {
			return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("discard new per subject requires discard new policy to be set"))
		}
		if cfg.MaxMsgsPer <= 0 {
			return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("discard new per subject requires max msgs per subject > 0"))
		}
	}

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Set AllowMsgTTL=false on the counter stream.
  2. Split workloads: use a separate stream for TTL'd messages, keeping the counter stream TTL-free.
  3. Or drop AllowMsgCounter and implement counters at the application level if TTLs are essential.
  4. Upgrade/align client and server versions and re-check docs, since these are recent JetStream features.

Example fix

// before
 StreamConfig{Name: "CTR", AllowMsgCounter: true, AllowMsgTTL: true}
// after
 StreamConfig{Name: "CTR", AllowMsgCounter: true, AllowMsgTTL: false}
Defensive patterns

Strategy: validation

Validate before calling

if cfg.AllowMsgCounter && cfg.AllowMsgTTL {
    return fmt.Errorf("counter streams cannot enable message TTLs")
}

Try / catch

var scErr *jetstream.JSApiError
if _, err := js.CreateStream(ctx, cfg); errors.As(err, &scErr) && strings.Contains(scErr.Description, "counter stream cannot use message TTLs") {
    cfg.AllowMsgTTL = false
    _, err = js.CreateStream(ctx, cfg)
}

Prevention

When it happens

Trigger: AddStream/UpdateStream with AllowMsgCounter=true and AllowMsgTTL=true, e.g. a stream that both increments counters and sets Nats-Msg-Ttl headers.

Common situations: Enabling all modern stream features (counters, msg TTLs, schedules) on one stream from a feature checklist; migrating a stream to counters without auditing existing publishers that send TTL headers.

Related errors


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