nats-io/nats-server · error · JSConsumerInvalidPolicyError

consumer delivery policy is deliver

Error message

consumer delivery policy is deliver %s, but optional start %s is also set

What it means

Consumer configuration validation detects a conflict between DeliverPolicy and an optional start position: the delivery policy already determines where to start (e.g. deliver all / last / new), but the user also supplied an explicit start (OptStartSeq or OptStartTime). The badStart helper formulates this error naming both the policy and the extra start option.

Solutions

  1. Remove the redundant field: either clear OptStartSeq/OptStartTime or choose the corresponding DeliverPolicy (DeliverByStartSequence / DeliverByStartTime).
  2. Construct a fresh ConsumerConfig per request instead of mutating a shared struct.
  3. Validate config client-side (config.Validate() via the client library) before sending the consumer create/update API request.

Example fix

// before
cc := nats.ConsumerConfig{DeliverPolicy: nats.DeliverAll, OptStartSeq: 42}
// after
cc := nats.ConsumerConfig{DeliverPolicy: nats.DeliverByStartSequence, OptStartSeq: 42}
Defensive patterns

Strategy: validation

Validate before calling

func startConflict(cc *nats.ConsumerConfig) error {
    switch cc.DeliverPolicy {
    case nats.DeliverAll, nats.DeliverLast, nats.DeliverNew, nats.DeliverLastPerSubject:
        if cc.OptStartSeq > 0 || !cc.OptStartTime.IsZero() {
            return fmt.Errorf("deliver policy %v conflicts with explicit start", cc.DeliverPolicy)
        }
    case nats.DeliverByStartSequence:
        if !cc.OptStartTime.IsZero() {
            return fmt.Errorf("start-seq policy conflicts with OptStartTime")
        }
    case nats.DeliverByStartTime:
        if cc.OptStartSeq > 0 {
            return fmt.Errorf("start-time policy conflicts with OptStartSeq")
        }
    }
    return nil
}

Try / catch

_, err := js.AddConsumer(stream, cc)
if err != nil && strings.Contains(err.Error(), "is also set") {
    cc.OptStartSeq, cc.OptStartTime = 0, time.Time{} // strip conflicting starts
    return js.AddConsumer(stream, cc)
}

Prevention

When it happens

Trigger: ConsumerConfig with DeliverPolicy: DeliverAll/Last/New plus OptStartSeq > 0; DeliverPolicy: DeliverByStartSequence plus OptStartTime set; DeliverPolicy: DeliverByStartTime plus OptStartSeq set — checked in the start-position conflict switch at consumer.go:918.

Common situations: Reusing a partially filled ConsumerConfig struct across requests so a previous OptStartSeq/OptStartTime lingers; building config from JSON where both fields were present; library defaults populating one field while code sets another.

Related errors


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

Appendix: source

Thrown at server/consumer.go:918

		}
	}
	subjectFilters := gatherSubjectFilters(config.FilterSubject, config.FilterSubjects)

	// Check subject filters do not overlap.
	for outer, subject := range subjectFilters {
		if !IsValidSubject(subject) {
			return NewJSStreamInvalidConfigError(ErrBadSubject)
		}
		for inner, ssubject := range subjectFilters {
			if inner != outer && subjectIsSubsetMatch(subject, ssubject) {
				return NewJSConsumerOverlappingSubjectFiltersError()
			}
		}
	}

	// Helper function to formulate similar errors.
	badStart := func(dp, start string) error {
		return fmt.Errorf("consumer delivery policy is deliver %s, but optional start %s is also set", dp, start)
	}
	notSet := func(dp, notSet string) error {
		return fmt.Errorf("consumer delivery policy is deliver %s, but optional %s is not set", dp, notSet)
	}

	// Check on start position conflicts.
	switch config.DeliverPolicy {
	case DeliverAll:
		if config.OptStartSeq > 0 {
			return NewJSConsumerInvalidPolicyError(badStart("all", "sequence"))
		}
		if config.OptStartTime != nil {
			return NewJSConsumerInvalidPolicyError(badStart("all", "time"))
		}
	case DeliverLast:
		if config.OptStartSeq > 0 {
			return NewJSConsumerInvalidPolicyError(badStart("last", "sequence"))
		}

View on GitHub (pinned to 3a66a489d2)