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
- Split same-subject expected-sequence writes across separate batches/requests.
- Wait for the first message on the subject to be acked/committed before publishing the next expected-sequence message.
- Remove the expected header for subsequent same-subject messages within a batch.
- 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
- At most one expected-per-subject-sequence message per subject per batch.
- Flush the batch between dependent same-subject writes.
- Serialize keyed writes through a single publisher.
- Audit batch code for duplicate keyed expected headers.
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.