nats-io/nats-server · error · JSStreamInvalidConfigError
duplicates window needs to be >= 100ms
Error message
duplicates window needs to be >= 100ms
What it means
A non-zero Duplicates window smaller than 100ms is rejected because duplicate detection granularity below 100ms is unreliable and adds cache churn without benefit. Validation in server/stream.go enforces a 100ms floor on any positive Duplicates value. Returned as a JSStreamInvalidConfigError.
Source
Thrown at server/stream.go:1986
}
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"))
}
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 {View on GitHub (pinned to 3a66a489d2)
Solutions
- Set Duplicates to at least 100ms (typically 2 minutes is fine).
- Set Duplicates to 0 to use the server default instead of a tiny value.
- If tests need fast settings, use >= 100ms or omit Duplicates.
- Round any computed window up to 100ms minimum before applying it.
Example fix
// before
StreamConfig{Name: "TEST", Duplicates: 10 * time.Millisecond}
// after
StreamConfig{Name: "TEST", Duplicates: 100 * time.Millisecond} Defensive patterns
Strategy: validation
Validate before calling
if cfg.Duplicates > 0 && cfg.Duplicates < 100*time.Millisecond {
cfg.Duplicates = 100 * time.Millisecond
} Try / catch
var scErr *jetstream.JSApiError
if _, err := js.CreateStream(ctx, cfg); errors.As(err, &scErr) && strings.Contains(scErr.Description, "duplicates window needs to be >= 100ms") {
cfg.Duplicates = 0 // use server default
_, err = js.CreateStream(ctx, cfg)
} Prevention
- Treat 100ms as the minimum; use 0 for default rather than tiny values.
- In tests, omit Duplicates instead of setting milliseconds-scale windows.
- Round computed windows up to the 100ms floor.
When it happens
Trigger: AddStream/UpdateStream with Duplicates set to a positive duration < 100ms, e.g. Duplicates: 10*time.Millisecond (note: exactly 0 is allowed and means 'use server default').
Common situations: Unit-test configs with tiny windows to 'keep memory low'; converting millisecond config fields to time.Duration incorrectly; assuming sub-100ms windows are permitted like other durations.
Related errors
- duplicates window can not be negative
- duplicates window can not be larger then max age
- 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/c000d4759f3ee70f.
Report an issue: GitHub.