nats-io/nats-server · error · JSStreamWrongLastSequenceConstantError
10164
10164
Error message
last sequence mismatch
What it means
Within a batched publish, an 'Nats-Expected-Last-Sequence' header was found on a message that is not the first message of the batch. The expected-last-sequence check is only meaningful for the first message in a batch (the batch as a whole commits after the previous sequence), so nats-server rejects the batch with JSStreamWrongLastSequenceConstantError (API error 10164).
Source
Thrown at server/jetstream_batching.go:755
counter.total = &initial
counter.sources = sources
counter.ops++
if diff.counter == nil {
diff.counter = map[string]*msgCounterRunningTotal{subject: counter}
} else {
diff.counter[subject] = counter
}
}
if len(hdr) > 0 {
// Expected last sequence.
if seq, exists := getExpectedLastSeq(hdr); exists && seq != mset.clseq-mset.clfs {
mlseq := mset.clseq - mset.clfs
err := fmt.Errorf("last sequence mismatch: %d vs %d", seq, mlseq)
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
}
View on GitHub (pinned to 3a66a489d2)
Solutions
- Set Nats-Expected-Last-Sequence only on the first message of the batch; remove it from subsequent messages
- Use Nats-Expected-Last-Sequence-Per-Subject for per-message expectations inside a batch instead of the global header
- If independent per-message guarantees are required, split into separate single publishes
- Update any header-cloning helper to strip expectation headers from non-leader batch messages
Example fix
// before
for i, m := range msgs {
m.Header.Set("Nats-Expected-Last-Sequence", expected) // set on every message
}
// after
for i, m := range msgs {
if i == 0 { m.Header.Set("Nats-Expected-Last-Sequence", expected) }
m.Header.Del("Nats-Expected-Last-Sequence") // keep only on the batch leader
} Defensive patterns
Strategy: validation
Validate before calling
for i, m := range msgs {
if i > 0 {
m.Header.Del("Nats-Expected-Last-Sequence") // only the batch leader may carry it
}
}
Type guard
func onlyFirstMsgExpectation(msgs []*nats.Msg) []*nats.Msg {
for i, m := range msgs {
if i > 0 { m.Header.Del("Nats-Expected-Last-Sequence") }
}
return msgs
}
Try / catch
err := js.PublishBatch(ctx, msgs)
var ae *nats.APIError
if errors.As(err, &ae) && ae.ErrorCode() == 10164 {
// fix batch: expected-last-sequence must be on the first message only
}
Prevention
- Strip expectation headers from all but the first message when building batches
- Use per-subject expectation headers for per-message checks inside batches
- Centralize batch construction in one helper so header rules are enforced in one place
When it happens
Trigger: Using atomic or fast batch publishing and setting Nats-Expected-Last-Sequence on a second or later message in the same batch; a batching helper that copies the header onto every message instead of only the first; reusing a prepared message template that already contains the header for all batch entries.
Common situations: Custom batch publish code that clones a header map for each message; middleware that injects idempotency/expectation headers per message; migrating single-message publish code to batch APIs without moving the expectation header to the batch leader.
Related errors
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/ac5a5d94a7787c3d.
Report an issue: GitHub.