nats-io/nats-server · error · JSStreamInvalidConfigError

duplicates window can not be negative

Error message

duplicates window can not be negative

What it means

JetStream stream config validation rejects a negative Duplicates window (time to cache recently published messages for duplicate detection). The library validates this in StreamConfig validation (server/stream.go) before creating or updating a stream, because a negative duration is meaningless. It is returned as a JSStreamInvalidConfigError so the client can correct the config.

Source

Thrown at server/stream.go:1975

	if cfg.Duplicates == 0 && cfg.Mirror == nil && len(cfg.Sources) == 0 {
		maxWindow := StreamDefaultDuplicatesWindow
		if lim.Duplicates > 0 && maxWindow > lim.Duplicates {
			if pedantic {
				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.

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Set StreamConfig.Duplicates to a non-negative duration (e.g. 2*time.Minute, the server default).
  2. If unset is desired, leave Duplicates at 0 so the server applies its default instead of a negative value.
  3. Sanitize externally supplied config: clamp Duplicates with max(0, value) before calling the API.
  4. Check code that computes the window via subtraction for accidental negation.

Example fix

// before
 cfg := jetstream.StreamConfig{Name: "ORDERS", Duplicates: -time.Second}
// after
 cfg := jetstream.StreamConfig{Name: "ORDERS", Duplicates: 2 * time.Minute}
Defensive patterns

Strategy: validation

Validate before calling

if cfg.Duplicates < 0 {
    return fmt.Errorf("duplicates window must be >= 0, got %s", cfg.Duplicates)
}

Try / catch

var scErr *jetstream.JSApiError
if _, err := js.CreateStream(ctx, cfg); errors.As(err, &scErr) && strings.Contains(scErr.Description, "duplicates window can not be negative") {
    cfg.Duplicates = 0 // let server apply default
    _, err = js.CreateStream(ctx, cfg)
}

Prevention

When it happens

Trigger: Creating or updating a stream via JetStream API (AddStream/UpdateStream or jetstream.CreateStream) with StreamConfig.Duplicates set to a negative time.Duration value, e.g. -1 * time.Second.

Common situations: Config parsed from JSON/YAML where a negative number was entered; code computing the window from a clock offset or subtraction that underflows; unmarshalling user-supplied API payloads directly into StreamConfig without pre-validation.

Related errors


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