nats-io/nats-server · error · JSPedanticError

duplicate window cannot be bigger than max age

Error message

duplicate window cannot be bigger than max age

What it means

In pedantic mode, if no explicit Duplicates window is set and the effective duplicate window (default or account-limited) is larger than the stream's MaxAge, the server rejects the config: a duplicate window longer than max age is meaningless since messages expire before dedup tracking ends. Non-pedantic mode silently sets Duplicates = MaxAge.

Source

Thrown at server/stream.go:1967

	}
	if cfg.MaxAge < 0 {
		return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("max age can not be negative"))
	}
	if cfg.MaxAge != 0 && cfg.MaxAge < 100*time.Millisecond {
		return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("max age needs to be >= 100ms"))
	}

	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 {

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Set cfg.Duplicates <= cfg.MaxAge explicitly
  2. Increase cfg.MaxAge to at least the desired duplicate window
  3. Omit pedantic mode so the server silently sets Duplicates=MaxAge

Example fix

// before
StreamConfig{Name: "ORDERS", MaxAge: time.Minute} // default window 2m > max age
// after
StreamConfig{Name: "ORDERS", MaxAge: time.Minute, Duplicates: time.Minute}
Defensive patterns

Strategy: validation

Validate before calling

if cfg.MaxAge != 0 && cfg.Duplicates == 0 {
    cfg.Duplicates = cfg.MaxAge // keep window <= max age
} else if cfg.Duplicates > cfg.MaxAge && cfg.MaxAge != 0 {
    cfg.Duplicates = cfg.MaxAge
}

Try / catch

if err != nil {
    var apiErr *nats.APIError
    if errors.As(err, &apiErr) && apiErr.ErrorCode == nats.ErrorCodeJetStreamInvalidStreamConfig {
        cfg.Duplicates = cfg.MaxAge
        err = createStream(cfg)
    }
}

Prevention

When it happens

Trigger: Pedantic stream create/update where cfg.Duplicates==0 (no mirror/sources), MaxAge is set and greater than 0 but smaller than maxWindow (StreamDefaultDuplicatesWindow, possibly capped by account limits). server/stream.go:1967.

Common situations: Streams configured with short retention (e.g. max_age=1m) that inherit the default 2-minute duplicate window; users unaware dedup window and max age interact; enabling pedantic validation exposing previously silent coercion.

Related errors


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