nats-io/nats-server · error · JSStreamOfflineReasonError

JS_STREAM_OFFLINE

JS_STREAM_OFFLINE

Error message

stream is offline

What it means

This JetStream API error (code JS_STREAM_OFFLINE) is returned on a stream info request when the stream exists but has an offlineReason set, meaning it is temporarily unavailable cluster-wide (e.g. created by a migration/restored asset not yet activated). The server responds via NewJSStreamOfflineReasonError wrapping the stream's specific offline reason.

Source

Thrown at server/jetstream_api.go:1640

		resp.Error = NewJSStreamMismatchError()
		s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
		return
	}

	// Handle clustered version here.
	if s.JetStreamIsClustered() {
		s.jsClusteredStreamUpdateRequest(ci, acc, subject, reply, copyBytes(rmsg), &cfg, ncfg.Pedantic)
		return
	}

	mset, err := acc.lookupStream(streamName)
	if err != nil {
		resp.Error = NewJSStreamNotFoundError(Unless(err))
		s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
		return
	}
	if mset.offlineReason != _EMPTY_ {
		resp.Error = NewJSStreamOfflineReasonError(errors.New(mset.offlineReason))
		s.sendDelayedAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp), nil, errRespDelay)
		return
	}

	// Update asset version metadata.
	setStaticStreamMetadata(&cfg)

	if err := mset.updatePedantic(&cfg, ncfg.Pedantic); err != nil {
		resp.Error = NewJSStreamUpdateError(err, Unless(err))
		s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
		return
	}

	msetCfg := mset.config()
	resp.StreamInfo = &StreamInfo{
		Created:   mset.createdTime(),
		State:     mset.state(),
		Config:    *setDynamicStreamMetadata(&msetCfg),

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Read the underlying offline reason in the API error payload for the specific cause.
  2. Wait for the stream's peers to come online / for restore activation to complete, then retry.
  3. If restored, activate the stream (complete the restore procedure) before issuing requests.
  4. If peers are permanently down, restore quorum or recreate the stream from backup.

Example fix

// before
si, err := js.StreamInfo("ORDERS") // JS_STREAM_OFFLINE
// after
si, err := js.StreamInfo("ORDERS")
if err != nil {
    var apiErr *nats.APIError
    if errors.As(err, &apiErr) && apiErr.ErrorCode == nats.JSStreamOffline {
        // poll/retry until the stream becomes online
        return waitForStreamOnline(js, "ORDERS", timeout)
    }
    return err
}
Defensive patterns

Strategy: retry

Validate before calling

func streamOnline(js nats.JetStreamContext, name string) error {
    _, err := js.StreamInfo(name)
    var apiErr *nats.APIError
    if errors.As(err, &apiErr) && apiErr.ErrorCode == nats.JSStreamOffline {
        return fmt.Errorf("stream %s offline: %s", name, apiErr.Description)
    }
    return err
}

Type guard

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

Try / catch

err := retry(3, backoff, func() error {
    _, err := js.StreamInfo("ORDERS")
    if isStreamOffline(err) { return err /* retryable */ }
    return nats.ErrEndOfData /* stop */
})

Prevention

When it happens

Trigger: Sending a stream info request ($JS.API.STREAM.INFO.<stream>) for a stream whose mset.offlineReason is non-empty; typically streams restored from backup/snapshot pending activation, or streams whose peers are all offline.

Common situations: Querying a stream restored via the restore API before it is activated; running info against a stream whose hosting peers are down; HA failover windows where the stream group has no online peers.

Related errors


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