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

  1. Lower StreamConfig.Duplicates to be <= MaxAge.
  2. Or raise MaxAge so it is >= the desired Duplicates window.
  3. Set Duplicates to 0 to let the server pick a compliant default.
  4. 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

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


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