nats-io/nats-server · error · JSStreamInvalidConfigError
counter stream cannot use discard new
Error message
counter stream cannot use discard new
What it means
A counter stream (AllowMsgCounter=true) tracks per-message counter values and relies on messages never being proactively evicted on publish, so the DiscardNew policy (discard oldest when limits reached) is incompatible. server/stream.go validation rejects Discard == DiscardNew for counter streams. Returned as a JSStreamInvalidConfigError.
Source
Thrown at server/stream.go:1996
if cfg.MaxAge != 0 && cfg.Duplicates > cfg.MaxAge {
return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("duplicates window can not be larger then max age"))
}
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 {View on GitHub (pinned to 3a66a489d2)
Solutions
- Set StreamConfig.Discard to DiscardOld when AllowMsgCounter is true.
- Or disable AllowMsgCounter if DiscardNew semantics are actually required.
- Rely on MaxMsgs/limits only with DiscardOld, or use limits retention with no discard pressure.
- Review newer NATS server versions' counter documentation for compatible discard policies.
Example fix
// before
StreamConfig{Name: "CTR", AllowMsgCounter: true, Discard: jetstream.DiscardNew}
// after
StreamConfig{Name: "CTR", AllowMsgCounter: true, Discard: jetstream.DiscardOld} Defensive patterns
Strategy: validation
Validate before calling
if cfg.AllowMsgCounter && cfg.Discard == jetstream.DiscardNew {
return fmt.Errorf("counter streams require Discard=DiscardOld")
} Try / catch
var scErr *jetstream.JSApiError
if _, err := js.CreateStream(ctx, cfg); errors.As(err, &scErr) && strings.Contains(scErr.Description, "counter stream cannot use discard new") {
cfg.Discard = jetstream.DiscardOld
_, err = js.CreateStream(ctx, cfg)
} Prevention
- Whenever adding AllowMsgCounter, set Discard=DiscardOld in the same change.
- Keep a validated stream-config constructor function instead of raw literals.
- Add a unit test asserting counter configs pass client-side validation.
When it happens
Trigger: AddStream/UpdateStream with AllowMsgCounter=true and Discard=DiscardNew (or DiscardPerSubject defaulting to DiscardNew semantics for counters).
Common situations: Building a counter/atomic-increment stream but leaving the common default DiscardNew in place; copying a normal stream config and adding AllowMsgCounter without removing DiscardNew.
Related errors
- 10052
- duplicates window can not be negative
- duplicates window can not be larger then max age
- duplicates window can not be larger then server limit of %v
- duplicates window needs to be >= 100ms
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/bc18704f6fe84777.
Report an issue: GitHub.