nats-io/nats-server · error · JSStreamInvalidConfigError
invalid discard policy
Error message
invalid discard policy
What it means
checkStreamCfgLocked: the stream's Discard policy failed to marshal/unmarshal as a valid DiscardPolicy enum — the configured discard value is not one of the recognized policies (old/new or limits/new). Surfaced as NewJSStreamInvalidConfigError; the API layer maps it to an invalid stream configuration error.
Source
Thrown at server/stream.go:1897
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
}
if cfg.Replicas > StreamMaxReplicas {
return cfg, NewJSStreamInvalidConfigError(fmt.Errorf("maximum replicas is %d", StreamMaxReplicas))View on GitHub (pinned to 3a66a489d2)
Solutions
- Set Discard to jetstream.DiscardOld or jetstream.DiscardNew explicitly.
- Omit the field to accept the default (DiscardOld).
- Validate the enum before submitting; log its numeric value if unsure.
- When using raw JSON requests, use the correct numeric enum value.
Example fix
// before
cfg := jetstream.StreamConfig{Name: "ORDERS", Discard: jetstream.DiscardPolicy(5)}
// after
cfg := jetstream.StreamConfig{Name: "ORDERS", Discard: jetstream.DiscardNew} Defensive patterns
Strategy: validation
Validate before calling
func validDiscard(d jetstream.DiscardPolicy) error {
switch d {
case jetstream.DiscardOld, jetstream.DiscardNew:
return nil
}
return fmt.Errorf("invalid discard policy: %d", int(d))
} Type guard
func isKnownDiscard(d jetstream.DiscardPolicy) bool {
return d == jetstream.DiscardOld || d == jetstream.DiscardNew
} Prevention
- Use only DiscardOld/DiscardNew constants.
- Omit Discard to accept the default (DiscardOld).
- Validate the field when building configs from untyped data.
- Note max-age/discard interplay only matters with valid enum values; validate early.
When it happens
Trigger: Passing an invalid cast value like DiscardPolicy(5), an uninitialized enum from an untyped struct, or decoding a config where 'discard' is a string ("old") instead of the expected enum in raw API JSON.
Common situations: Config templating that interpolates wrong types into the discard field; cross-version enum mismatches; copy-pasted constants from other messaging systems.
Related errors
- invalid retention
- 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/797f1e855b34bd9f.
Report an issue: GitHub.