nats-io/nats-server · error

deliver policy can not be updated

Error message

deliver policy can not be updated

What it means

checkNewConsumerConfig compares the existing consumer config with the proposed update and rejects changes to immutable fields. DeliverPolicy (DeliverAll, DeliverLast, DeliverByStartSequence, DeliverByStartTime, etc.) cannot change after a durable consumer is created because it determines where the consumer begins consuming. The update request fails with this error.

Source

Thrown at server/consumer.go:2535

		acc.mu.RUnlock()
		if mpay > 0 {
			burst = int(mpay)
		} else {
			burst = int(acc.srv.getOpts().MaxPayload)
		}
	}

	o.rlimit = rate.NewLimiter(rl, burst)
}

// Check if new consumer config allowed vs old.
func (acc *Account) checkNewConsumerConfig(cfg, ncfg *ConsumerConfig) error {
	if reflect.DeepEqual(cfg, ncfg) {
		return nil
	}
	// Something different, so check since we only allow certain things to be updated.
	if cfg.DeliverPolicy != ncfg.DeliverPolicy {
		return errors.New("deliver policy can not be updated")
	}
	if cfg.MemoryStorage != ncfg.MemoryStorage {
		return errors.New("storage type can not be updated")
	}
	// Direct and Sourcing classify the consumer for its whole lifetime, which the
	// stream relies on when walking its consumer list, so they can not change.
	if cfg.Direct != ncfg.Direct {
		return errors.New("direct can not be updated")
	}
	if cfg.Sourcing != ncfg.Sourcing {
		return errors.New("sourcing can not be updated")
	}
	if cfg.OptStartSeq != ncfg.OptStartSeq {
		return errors.New("start sequence can not be updated")
	}
	if cfg.OptStartTime != nil && ncfg.OptStartTime != nil {
		// Both have start times set, compare them directly:
		if !cfg.OptStartTime.Equal(*ncfg.OptStartTime) {

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Delete the consumer (js.DeleteConsumer / $JS.API.CONSUMER.DELETE) and recreate it with the desired DeliverPolicy.
  2. Use a new durable name for the new policy and migrate consumers to it.
  3. Revert the config change so DeliverPolicy matches the existing consumer.
  4. Read the existing config first (js.ConsumerInfo) and only send fields you intend to change.

Example fix

// before
js.UpdateConsumer(stream, &nats.ConsumerConfig{Durable: "worker", DeliverPolicy: nats.DeliverLast})
// after
js.DeleteConsumer(stream, "worker")
js.AddConsumer(stream, &nats.ConsumerConfig{Durable: "worker", DeliverPolicy: nats.DeliverLast})
Defensive patterns

Strategy: validation

Validate before calling

func safeUpdate(existing, desired *nats.ConsumerConfig) (*nats.ConsumerConfig, error) {
	if existing.DeliverPolicy != desired.DeliverPolicy {
		return nil, fmt.Errorf("deliver policy cannot be updated; delete and recreate consumer %q", existing.Durable)
	}
	return desired, nil
}

Prevention

When it happens

Trigger: Calling js.UpdateConsumer or the $JS.API.CONSUMER.CREATE API with an existing durable name but a different DeliverPolicy than the stored config.

Common situations: Changing an app from DeliverLast to DeliverNew during redeployment; reusing an old durable name with different start semantics after a refactor; CI applying config drift from a template with a different deliver policy.

Related errors


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