nats-io/nats-server · error · JSStreamWrongLastSequenceConstantError

JS_STREAM_WRONG_LAST_SEQUENCE

JS_STREAM_WRONG_LAST_SEQUENCE

Error message

last sequence by subject mismatch

What it means

checkMsgHeadersPreClusteredProposal rejects a message carrying an expected last-sequence-per-subject header when that subject already has a write inflight in the current batch; expected checks would be incorrect, so the proposal is rejected with JS_STREAM_WRONG_LAST_SEQUENCE ('last sequence by subject mismatch').

Source

Thrown at server/jetstream_batching.go:770

			return hdr, msg, 0, NewJSStreamWrongLastSequenceError(mlseq), err
		} else if exists && len(diff.inflight) > 0 {
			// Only the first message in a batch can contain an expected last sequence.
			err := fmt.Errorf("last sequence mismatch")
			return hdr, msg, 0, NewJSStreamWrongLastSequenceConstantError(), err
		}

		// Expected last sequence per subject.
		if seq, exists := getExpectedLastSeqPerSubject(hdr); exists {
			// Allow override of the subject used for the check.
			seqSubj := subject
			if optSubj := getExpectedLastSeqPerSubjectForSubject(hdr); optSubj != _EMPTY_ {
				seqSubj = copyString(optSubj)
			}

			// The subject is already written to in this batch, we can't allow
			// expected checks since they would be incorrect.
			if _, ok := diff.inflight[seqSubj]; ok {
				err := errors.New("last sequence by subject mismatch")
				return hdr, msg, 0, NewJSStreamWrongLastSequenceConstantError(), err
			}

			// If the subject is already in process, block as otherwise we could have
			// multiple messages inflight with the same subject.
			if _, found := mset.expectedPerSubjectInProcess[seqSubj]; found {
				err := errors.New("last sequence by subject mismatch")
				return hdr, msg, 0, NewJSStreamWrongLastSequenceConstantError(), err
			}

			// If the subject is already in process but without expected headers, block as we would have
			// multiple messages inflight with the same subject.
			if _, ok := mset.inflight[seqSubj]; ok {
				err := errors.New("last sequence by subject mismatch")
				return hdr, msg, 0, NewJSStreamWrongLastSequenceConstantError(), err
			}

			// If we've already done an expected-check on this subject, use the cached result.

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Split same-subject expected-sequence writes across separate batches/requests.
  2. Wait for the first message on the subject to be acked/committed before publishing the next expected-sequence message.
  3. Remove the expected header for subsequent same-subject messages within a batch.
  4. Retry the rejected publish alone after the batch commits.

Example fix

// before: two same-subject expected checks in one batch
batch.Publish("counters.a", nil, nats.ExpectedLastSeqPerSubject(10))
batch.Publish("counters.a", nil, nats.ExpectedLastSeqPerSubject(11)) // mismatch: subject inflight
// after: commit the first write before the next check
batch.Publish("counters.a", nil, nats.ExpectedLastSeqPerSubject(10))
batch.Flush()
js.Publish("counters.a", nil, nats.ExpectedLastSeqPerSubject(11))
Defensive patterns

Strategy: validation

Validate before calling

// reject same-subject duplicates carrying expected-sequence headers before adding to a batch
seen := map[string]struct{}{}
func canBatch(subj string, hasExpectedHeader bool) bool {
	if hasExpectedHeader {
		if _, dup := seen[subj]; dup {
			return false // would be rejected: subject already inflight in batch
		}
	}
	seen[subj] = struct{}{}
	return true
}

Try / catch

err := batch.Flush()
var apiErr *nats.APIError
if errors.As(err, &apiErr) && apiErr.ErrorCode == nats.JSStreamWrongLastSequence {
	js.Publish(subj, data, nats.ExpectedLastSeqPerSubject(seq)) // resend alone after commit
}

Prevention

When it happens

Trigger: Two messages with Nats-Expected-Last-Subject-Sequence for the same subject inside one batched/atomic publish; the second finds the subject in diff.inflight and is rejected.

Common situations: Counter-style workloads publishing duplicate keyed expected-sequence messages in a single batch; concurrent publishers racing on the same subject within one batch context.

Related errors


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