nats-io/nats-server · error · JSStreamInvalidConfigError

invalid retention

Error message

invalid retention

What it means

cfg.Retention (a RetentionPolicy enum) failed to marshal to JSON inside checkStreamCfgLocked, meaning it holds a value outside the known retention policies (Limits, Interest, WorkQueue). The config is rejected as 'invalid retention'.

Source

Thrown at server/stream.go:1894

	if len(config.Name) > JSMaxNameLen {
		return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("stream name is too long, maximum allowed is %d", JSMaxNameLen))
	}
	if len(config.Description) > JSMaxDescriptionLen {
		return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("stream description is too long, maximum allowed is %d", JSMaxDescriptionLen))
	}

	var metadataLen int
	for k, v := range config.Metadata {
		metadataLen += len(k) + len(v)
	}
	if metadataLen > JSMaxMetadataLen {
		return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("stream metadata exceeds maximum size of %d bytes", JSMaxMetadataLen))
	}

	cfg := *config

	if _, err := cfg.Retention.MarshalJSON(); err != nil {
		return cfg, NewJSStreamInvalidConfigError(fmt.Errorf("invalid retention"))
	}
	if _, err := cfg.Discard.MarshalJSON(); err != nil {
		return cfg, NewJSStreamInvalidConfigError(fmt.Errorf("invalid discard policy"))
	}
	if _, err := cfg.Compression.MarshalJSON(); err != nil {
		return cfg, NewJSStreamInvalidConfigError(fmt.Errorf("invalid compression"))
	}

	// Make file the default.
	if cfg.Storage == 0 {
		cfg.Storage = FileStorage
	}
	if _, err := cfg.Storage.MarshalJSON(); err != nil {
		return cfg, NewJSStreamInvalidConfigError(fmt.Errorf("invalid storage type"))
	}

	if cfg.Replicas == 0 {
		cfg.Replicas = 1

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Set Retention to one of jetstream.LimitsPolicy, InterestPolicy, or WorkQueuePolicy.
  2. If sending raw JSON, omit 'retention' to use the default (Limits) or use the numeric enum value the server expects.
  3. Validate the enum value before building the config.
  4. Check client/server version skew for enum value mismatches.

Example fix

// before
cfg := jetstream.StreamConfig{Name: "ORDERS", Retention: jetstream.RetentionPolicy(7)}
// after
cfg := jetstream.StreamConfig{Name: "ORDERS", Retention: jetstream.WorkQueuePolicy}
Defensive patterns

Strategy: validation

Validate before calling

func validRetention(r jetstream.RetentionPolicy) error {
  switch r {
  case jetstream.LimitsPolicy, jetstream.InterestPolicy, jetstream.WorkQueuePolicy:
    return nil
  }
  return fmt.Errorf("invalid retention policy: %d", int(r))
}

Type guard

func isKnownRetention(r jetstream.RetentionPolicy) bool {
  return r == jetstream.LimitsPolicy || r == jetstream.InterestPolicy || r == jetstream.WorkQueuePolicy
}

Prevention

When it happens

Trigger: Passing an out-of-range integer cast to RetentionPolicy, a zero-value from an uninitialized struct where the enum does not include 0, or a retention value decoded from a config file that doesn't match any valid policy.

Common situations: Hand-writing JSON like "retention": "limit" (string instead of numeric enum) sent directly to the API; copy-pasting RetentionPolicy values across client library versions; Go clients constructing StreamConfig{Retention: RetentionPolicy(7)}.

Related errors


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