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 = 1View on GitHub (pinned to 3a66a489d2)
Solutions
- Set Retention to one of jetstream.LimitsPolicy, InterestPolicy, or WorkQueuePolicy.
- If sending raw JSON, omit 'retention' to use the default (Limits) or use the numeric enum value the server expects.
- Validate the enum value before building the config.
- 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
- Always set Retention from named constants, never integer casts.
- Omit the field to accept the default Limits policy.
- Validate enum values when decoding configs from files/JSON.
- Watch for version skew between client enum values and server.
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
- invalid discard policy
- invalid compression
- invalid storage type
- JS_STREAM_MSG_DELETE_FAILED
- JS_STREAM_PURGE_FAILED
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/94c3130a5e6a6e84.
Report an issue: GitHub.