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

  1. Set Duplicates to at least 100ms (typically 2 minutes is fine).
  2. Set Duplicates to 0 to use the server default instead of a tiny value.
  3. If tests need fast settings, use >= 100ms or omit Duplicates.
  4. 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

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


AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02). Data as JSON: /api/errors/c000d4759f3ee70f. Report an issue: GitHub.