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
- Raise MaxAge to at least 100*time.Millisecond
- If you truly need short retention, keep a floor of 100ms in your config layer
- 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
- Enforce a 100ms floor constant in your config layer
- Review unit conversions (ms vs seconds) wherever durations are built
- Avoid sub-100ms retention even in tests; scale tests differently
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
- max age can not be negative
- max_expires must not be negative
- stream republish transform from '%s' to '%s': %w
- maximum replicas is %d
- max_msgs must be set to -1
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/395a73128c206cde.
Report an issue: GitHub.