nats-io/nats-server · error · JSStreamInvalidConfigError
duplicates window can not be larger then max age
Error message
duplicates window can not be larger then max age
What it means
JetStream requires the Duplicates window (duplicate message cache TTL) to be no larger than the stream's MaxAge, otherwise a message could be considered a duplicate after it was already expired. StreamConfig validation in server/stream.go rejects Duplicates > MaxAge when MaxAge is set. Returned as a JSStreamInvalidConfigError.
Source
Thrown at server/stream.go:1979
return StreamConfig{}, NewJSPedanticError(fmt.Errorf("duplicate window limits are higher than current limits"))
}
maxWindow = lim.Duplicates
}
if cfg.MaxAge != 0 && cfg.MaxAge < maxWindow {
if pedantic {
return StreamConfig{}, NewJSPedanticError(fmt.Errorf("duplicate window cannot be bigger than max age"))
}
cfg.Duplicates = cfg.MaxAge
} else {
cfg.Duplicates = maxWindow
}
}
if cfg.Duplicates < 0 {
return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("duplicates window can not be negative"))
}
// Check that duplicates is not larger then age if set.
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"))
}View on GitHub (pinned to 3a66a489d2)
Solutions
- Lower StreamConfig.Duplicates to be <= MaxAge.
- Or raise MaxAge so it is >= the desired Duplicates window.
- Set Duplicates to 0 to let the server pick a compliant default.
- On stream update, adjust both values in the same API call so validation passes.
Example fix
// before
StreamConfig{Name: "EVENTS", MaxAge: time.Minute, Duplicates: 2 * time.Minute}
// after
StreamConfig{Name: "EVENTS", MaxAge: time.Minute, Duplicates: time.Minute} Defensive patterns
Strategy: validation
Validate before calling
if cfg.MaxAge != 0 && cfg.Duplicates > cfg.MaxAge {
return fmt.Errorf("duplicates (%s) must be <= max age (%s)", cfg.Duplicates, cfg.MaxAge)
} Try / catch
var scErr *jetstream.JSApiError
if _, err := js.UpdateStream(ctx, cfg); errors.As(err, &scErr) && strings.Contains(scErr.Description, "duplicates window can not be larger then max age") {
cfg.Duplicates = cfg.MaxAge
_, err = js.UpdateStream(ctx, cfg)
} Prevention
- Change MaxAge and Duplicates together in the same update call.
- When lowering MaxAge, first verify the current Duplicates window fits.
- Default to Duplicates=0 so the server derives a compliant value.
When it happens
Trigger: AddStream/UpdateStream with MaxAge set (non-zero) and Duplicates greater than MaxAge, e.g. Duplicates: 2*time.Minute, MaxAge: time.Minute.
Common situations: Setting a short retention (MaxAge) for ephemeral data while keeping the default 2-minute duplicates window; tuning MaxAge down later without lowering Duplicates; template/accounts defaults where Duplicates is inherited implicitly.
Related errors
- duplicates window can not be negative
- duplicates window needs to be >= 100ms
- max_ack_pending must be set to -1
- stream republish transform from '%s' to '%s': %w
- maximum replicas is %d
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/d19d0effca99b8e9.
Report an issue: GitHub.