nats-io/nats-server · error · JSStreamInvalidConfigError

max age needs to be >= 100ms

Error message

max age needs to be >= 100ms

What it means

JetStream requires MaxAge, when set, to be at least 100ms; shorter values are rejected with JSStreamInvalidConfigError. This enforces a sane lower bound on message retention so the server's internal expiry machinery behaves correctly.

Source

Thrown at server/stream.go:1954

		cfg.MaxBytes = -1
	}
	if cfg.MaxMsgSize == 0 || cfg.MaxMsgSize < -1 {
		if pedantic && cfg.MaxMsgSize < -1 {
			return StreamConfig{}, NewJSPedanticError(fmt.Errorf("max_msg_size must be set to -1"))
		}
		cfg.MaxMsgSize = -1
	}
	if cfg.MaxConsumers == 0 || cfg.MaxConsumers < -1 {
		if pedantic && cfg.MaxConsumers < -1 {
			return StreamConfig{}, NewJSPedanticError(fmt.Errorf("max_consumers must be set to -1"))
		}
		cfg.MaxConsumers = -1
	}
	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
		}

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Raise MaxAge to at least 100*time.Millisecond
  2. If you truly need short retention, keep a floor of 100ms in your config layer
  3. Check unit conversions (ms vs s) in code that builds the duration

Example fix

// before
MaxAge: 50 * time.Millisecond
// after
MaxAge: 100 * time.Millisecond
Defensive patterns

Strategy: validation

Validate before calling

if cfg.MaxAge != 0 && cfg.MaxAge < 100*time.Millisecond {
    return fmt.Errorf("MaxAge must be >= 100ms; got %v", cfg.MaxAge)
}

Try / catch

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

Prevention

When it happens

Trigger: Stream create/update with 0 < MaxAge < 100*time.Millisecond, e.g. MaxAge: 50*time.Millisecond or a duration computed in the wrong time unit (seconds vs milliseconds confusion). server/stream.go:1954.

Common situations: Unit conversion bugs (intended 100s but wrote 100*time.Millisecond); tests using tiny TTLs to speed up expiry; dynamic configs from user input with millisecond inputs intended as seconds.

Related errors


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