nats-io/nats-server · error · JSConsumerOfflineReasonError

JS_CONSUMER_OFFLINE

JS_CONSUMER_OFFLINE

Error message

consumer is offline

What it means

JS_CONSUMER_OFFLINE is returned on consumer create/update when the named durable consumer exists but its asset is offline (offlineReason set) — e.g. its raft group lacks quorum or the asset is unsupported. The consumer cannot be served or modified until it comes back online.

Source

Thrown at server/jetstream_api.go:4979

			req.Config.Name = fmt.Sprintf("%s-%s", req.Config.Name, createConsumerName())
			consumerName = req.Config.Name
		}
	}

	// If the user provided an expected stream identity, reject the request on a mismatch.
	var streamIdentity string
	if req.Config.Direct || req.Config.Sourcing {
		streamIdentity = stream.identity()
		if reqIdentity := sliceHeader(JSStreamIdentity, hdr); len(reqIdentity) > 0 && bytesToString(reqIdentity) != streamIdentity {
			resp.Error = NewJSConsumerStreamIdentityMismatchError(streamIdentity)
			s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
			return
		}
	}

	if o := stream.lookupConsumer(consumerName); o != nil {
		if o.offlineReason != _EMPTY_ {
			resp.Error = NewJSConsumerOfflineReasonError(errors.New(o.offlineReason))
			s.sendDelayedAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp), nil, errRespDelay)
			return
		}
		// If the consumer already exists then don't allow updating the PauseUntil, just set
		// it back to whatever the current configured value is.
		o.mu.RLock()
		req.Config.PauseUntil = o.cfg.PauseUntil
		// If a durable sourcing consumer is used, we need to reset the deliver policy.
		if req.Config.Sourcing && req.Config.Durable != _EMPTY_ {
			req.Config.DeliverPolicy = o.cfg.DeliverPolicy
			req.Config.OptStartSeq = o.cfg.OptStartSeq
			req.Config.OptStartTime = o.cfg.OptStartTime
		}
		o.mu.RUnlock()
	}

	// Initialize/update asset version metadata.
	setStaticConsumerMetadata(&req.Config)

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Restore quorum for the consumer's raft group (start the required JetStream servers) and wait for leader election.
  2. Read the offlineReason in the API error and server logs, then address that root cause.
  3. If unsupported after downgrade, roll forward to the newer version or recreate the consumer with a compatible config.
  4. Retry once `nats consumer info <stream> <consumer>` succeeds without the offline error.

Example fix

// before
_, err := js.AddConsumer("ORDERS", &nats.ConsumerConfig{Durable: "proc"})
if err != nil {
	var apiErr *nats.APIError
	if errors.As(err, &apiErr) && apiErr.ErrorCode == nats.JSConsumerOfflineErr {
		// restore quorum / roll forward, then retry
	}
}
Defensive patterns

Strategy: validation

Validate before calling

ci, err := js.ConsumerInfo("ORDERS", "proc")
if err != nil {
	var apiErr *nats.APIError
	if errors.As(err, &apiErr) && apiErr.ErrorCode == nats.JSConsumerOfflineErr {
		return fmt.Errorf("consumer offline: %s", apiErr.Description)
	}
}

Type guard

func isConsumerOfflineErr(err error) bool {
	var apiErr *nats.APIError
	return errors.As(err, &apiErr) && apiErr.ErrorCode == nats.JSConsumerOfflineErr
}

Try / catch

_, err := js.UpdateConsumer("ORDERS", cfg)
if isConsumerOfflineErr(err) {
	// restore quorum / wait for leader, then retry with backoff
}

Prevention

When it happens

Trigger: Consumer create/update while the consumer's raft group has no quorum or leader; consumer asset marked offline after a version downgrade; request arrives during failover before the new leader is elected.

Common situations: Majority of cluster nodes down during maintenance; rolling downgrade leaving the consumer unsupported; newly restored cluster with pending leader elections.

Related errors


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