nats-io/nats-server · error · JSConsumerInvalidPolicyError

10094

10094

Error message

consumer delivery policy is deliver last per subject, but FilterSubject is not set

What it means

JSConsumerInvalidPolicyError (API error 10094): the consumer create/update handler rejects a configuration whose DeliverPolicy is DeliverLastPerSubject without any subject filter. DeliverLastPerSubject needs a filter to know which subjects to track; with no FilterSubject/FilterSubjects it is an unresolvable policy, so the request fails.

Source

Thrown at server/jetstream_cluster.go:11435

						total++
						if selectedTier == _EMPTY_ || isSameTier(ca.Config.replicas(&streamCfg), selectedReplicas) {
							tierTotal++
						}
					}
				}
				if (streamMaxc > 0 && total >= streamMaxc) || (tierMaxc > 0 && tierTotal >= tierMaxc) {
					resp.Error = NewJSMaximumConsumersLimitError()
					s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
					return
				}
			}
		}
	}

	// Also short circuit if DeliverLastPerSubject is set with no FilterSubject.
	if cfg.DeliverPolicy == DeliverLastPerSubject {
		if cfg.FilterSubject == _EMPTY_ && len(cfg.FilterSubjects) == 0 {
			resp.Error = NewJSConsumerInvalidPolicyError(fmt.Errorf("consumer delivery policy is deliver last per subject, but FilterSubject is not set"))
			s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
			return
		}
	}

	// Setup proper default for ack wait if we are in explicit ack mode.
	if cfg.AckWait == 0 && (cfg.AckPolicy == AckExplicit || cfg.AckPolicy == AckAll) {
		cfg.AckWait = JsAckWaitDefault
	}
	// Setup default of -1, meaning no limit for MaxDeliver.
	if cfg.MaxDeliver == 0 {
		cfg.MaxDeliver = -1
	}
	// Set proper default for max ack pending if we are ack explicit and none has been set.
	if cfg.AckPolicy == AckExplicit && cfg.MaxAckPending == 0 {
		cfg.MaxAckPending = JsDefaultMaxAckPending
	}

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Set FilterSubject (e.g. `orders.>`) in the consumer config, or add entries to FilterSubjects.
  2. If no filter is wanted, switch DeliverPolicy to DeliverLast or DeliverAll instead of DeliverLastPerSubject.
  3. Add a pre-submission validation that pairs DeliverLastPerSubject with a non-empty filter.

Example fix

// before
cc := &nats.ConsumerConfig{DeliverPolicy: nats.DeliverLastPerSubject}
// after
cc := &nats.ConsumerConfig{
    DeliverPolicy: nats.DeliverLastPerSubject,
    FilterSubject: "events.>",
}
Defensive patterns

Strategy: validation

Validate before calling

// Go client: guard before creating the consumer
if cc.DeliverPolicy == nats.DeliverLastPerSubject && cc.FilterSubject == "" && len(cc.FilterSubjects) == 0 {
    return errors.New("DeliverLastPerSubject requires FilterSubject or FilterSubjects")
}

Type guard

func validDeliverLastPerSubject(cc *nats.ConsumerConfig) bool {
    if cc.DeliverPolicy != nats.DeliverLastPerSubject {
        return true
    }
    return cc.FilterSubject != "" || len(cc.FilterSubjects) > 0
}

Try / catch

_, err := js.CreateConsumer(nc, stream, cfg)
var apiErr *nats.APIError
if errors.As(err, &apiErr) && apiErr.ErrorCode == 10094 {
    cfg.FilterSubject = "events.>" // or switch DeliverPolicy
    _, err = js.CreateConsumer(nc, stream, cfg)
}

Prevention

When it happens

Trigger: Creating a consumer (push or pull) via `$JS.API.CONSUMER.CREATE.*`, `jetstream.CreateConsumer`, or `js.AddConsumer` with `DeliverPolicy: DeliverLastPerSubject` while leaving `FilterSubject` empty (and no `FilterSubjects`).

Common situations: Copy-pasted consumer configs where the filter was removed; code paths that compute the deliver policy dynamically but only sometimes set a filter; tooling migrating consumers to 'last per subject' semantics.

Related errors


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