nats-io/nats-server · error · ApiError

first backoff value has to equal batch AckWait

Error message

first backoff value has to equal batch AckWait

What it means

A JetStream pedantic validation error thrown when BackOff is configured but its first element does not equal AckWait. BackOff overrides AckWait (and MaxDeliver), and the invariant is BackOff[0] == AckWait; pedantic mode enforces it instead of silently overwriting AckWait with BackOff[0] at server/consumer.go:680.

Source

Thrown at server/consumer.go:680

	if config.PinnedTTL < 0 {
		if pedantic {
			return NewJSPedanticError(errors.New("priority_timeout must not be negative"))
		}
		config.PinnedTTL = 0
	}

	// Set to default if not specified.
	if config.DeliverSubject == _EMPTY_ && config.MaxWaiting == 0 {
		config.MaxWaiting = JSWaitQueueDefaultMax
	}
	// Setup proper default for ack wait if we are in explicit ack mode.
	if config.AckWait == 0 && (config.AckPolicy == AckExplicit || config.AckPolicy == AckAll) {
		config.AckWait = JsAckWaitDefault
	}
	// If BackOff was specified that will override the AckWait and the MaxDeliver.
	if len(config.BackOff) > 0 {
		if pedantic && config.AckWait != config.BackOff[0] {
			return NewJSPedanticError(errors.New("first backoff value has to equal batch AckWait"))
		}
		config.AckWait = config.BackOff[0]
	}
	if config.MaxAckPending == 0 {
		if pedantic && streamCfg.ConsumerLimits.MaxAckPending > 0 {
			return NewJSPedanticError(errors.New("max_ack_pending must be set if it's configured in stream limits"))
		}
		config.MaxAckPending = streamCfg.ConsumerLimits.MaxAckPending
	}
	if config.InactiveThreshold == 0 {
		if pedantic && streamCfg.ConsumerLimits.InactiveThreshold > 0 {
			return NewJSPedanticError(errors.New("inactive_threshold must be set if it's configured in stream limits"))
		}
		config.InactiveThreshold = streamCfg.ConsumerLimits.InactiveThreshold
	}
	// Set proper default for max ack pending if we are ack explicit and none has been set.
	if config.MaxAckPending == 0 && config.AckPolicy != AckNone {
		ackPending := JsDefaultMaxAckPending

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Set AckWait equal to BackOff[0], or simply omit AckWait (0) and let BackOff define it.
  2. Derive AckWait programmatically: cc.AckWait = cc.BackOff[0] after building the schedule.
  3. Check tooling/UIs that set AckWait independently from BackOff and keep them in sync.
  4. Run Validate(true) client-side to catch the mismatch before the server call.

Example fix

// before
cc := nats.ConsumerConfig{
    AckWait: 30 * time.Second,
    BackOff: []time.Duration{time.Minute, 5 * time.Minute},
}
// after
backoff := []time.Duration{time.Minute, 5 * time.Minute}
cc := nats.ConsumerConfig{
    AckWait: backoff[0],
    BackOff: backoff,
}
Defensive patterns

Strategy: validation

Validate before calling

if len(cfg.BackOff) > 0 && cfg.AckWait != 0 && cfg.AckWait != cfg.BackOff[0] {
    return errors.New("AckWait must equal BackOff[0] (or be unset)")
}

Try / catch

if _, err := js.AddConsumer(stream, &cc); err != nil && strings.Contains(err.Error(), "first backoff value") {
    cc.AckWait = cc.BackOff[0]
    _, err = js.AddConsumer(stream, &cc)
}

Prevention

When it happens

Trigger: ConsumerConfig with len(BackOff) > 0, AckWait != 0, and AckWait != BackOff[0], e.g. AckWait: 30*time.Second with BackOff: []time.Duration{1*time.Minute, 5*time.Minute}, submitted with pedantic validation.

Common situations: Teams migrating from plain AckWait+MaxDeliver to exponential BackOff schedules but leaving the old AckWait value; computing backoff arrays programmatically without syncing the first element; older configs that were previously accepted via silent clamping.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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