{"record":{"id":"a6da51da4dc1d572","repo":"nats-io/nats-server","slug":"10071","errorCode":"10071","errorMessage":"last sequence mismatch: %d vs %d","messagePattern":"last sequence mismatch: (.+?) vs (.+?)","errorType":"error_code","errorClass":"JSStreamWrongLastSequenceError","httpStatus":400,"severity":"error","filePath":"server/jetstream_batching.go","lineNumber":751,"sourceCode":"\t\t// Keep the in-memory counters up-to-date.\n\t\tif counter == nil {\n\t\t\tcounter = &msgCounterRunningTotal{}\n\t\t}\n\t\tcounter.total = &initial\n\t\tcounter.sources = sources\n\t\tcounter.ops++\n\t\tif diff.counter == nil {\n\t\t\tdiff.counter = map[string]*msgCounterRunningTotal{subject: counter}\n\t\t} else {\n\t\t\tdiff.counter[subject] = counter\n\t\t}\n\t}\n\n\tif len(hdr) > 0 {\n\t\t// Expected last sequence.\n\t\tif seq, exists := getExpectedLastSeq(hdr); exists && seq != mset.clseq-mset.clfs {\n\t\t\tmlseq := mset.clseq - mset.clfs\n\t\t\terr := fmt.Errorf(\"last sequence mismatch: %d vs %d\", seq, mlseq)\n\t\t\treturn hdr, msg, 0, NewJSStreamWrongLastSequenceError(mlseq), err\n\t\t} else if exists && len(diff.inflight) > 0 {\n\t\t\t// Only the first message in a batch can contain an expected last sequence.\n\t\t\terr := fmt.Errorf(\"last sequence mismatch\")\n\t\t\treturn hdr, msg, 0, NewJSStreamWrongLastSequenceConstantError(), err\n\t\t}\n\n\t\t// Expected last sequence per subject.\n\t\tif seq, exists := getExpectedLastSeqPerSubject(hdr); exists {\n\t\t\t// Allow override of the subject used for the check.\n\t\t\tseqSubj := subject\n\t\t\tif optSubj := getExpectedLastSeqPerSubjectForSubject(hdr); optSubj != _EMPTY_ {\n\t\t\t\tseqSubj = copyString(optSubj)\n\t\t\t}\n\n\t\t\t// The subject is already written to in this batch, we can't allow\n\t\t\t// expected checks since they would be incorrect.\n\t\t\tif _, ok := diff.inflight[seqSubj]; ok {","sourceCodeStart":733,"sourceCodeEnd":769,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/server/jetstream_batching.go#L733-L769","documentation":"The publish carried an 'Nats-Expected-Last-Sequence' header whose value does not match the stream's last committed sequence (clseq minus any failed sequences, clfs). nats-server rejects the message with JSStreamWrongLastSequenceError (API error 10071) to enforce optimistic-concurrency publish (dedupe/last-sequence guarantee) before a clustered proposal is made. The message includes both the client-supplied and server-side expected values.","triggerScenarios":"Publishing with Nats-Expected-Last-Sequence set when: another publisher appended messages between the client's read of the sequence and its publish; the client cached a stale sequence from a previous response; publish races on a stream that also receives messages via mirrors/sources or JS API; sequence computed manually instead of from a PubAck.","commonSituations":"Optimistic concurrency (idempotent/atomic writes) on busy streams where concurrent publishers frequently win the race; clients replaying a saved sequence after reconnect; per-subject expectation used where global expected-last-sequence was needed; stream purge/rollup reset the sequence but the client kept the old value.","solutions":["Fetch the actual last sequence (stream info, or use the per-subject variant) and retry the publish with the corrected Nats-Expected-Last-Sequence value","Treat the returned JSStreamWrongLastSequenceError as a retryable conflict: re-read last sequence and republish with backoff, ideally with jitter to avoid livelock among competing publishers","If pure sequence correctness (not atomicity) is required, drop the header and rely on Nats-Msg-Id dedupe instead","Check for a purge/rollup or mirror/source that reset stream sequences and re-sync client state"],"exampleFix":"// before\njs.PublishMsg(&nats.Msg{Subject: subj, Data: data, Header: nats.Header{\"Nats-Expected-Last-Sequence\": []string{\"42\"}}})\n// after\nfor {\n    si, _ := js.StreamInfo(streamName)\n    last := si.State.LastSeq\n    _, err := js.PublishMsg(&nats.Msg{Subject: subj, Data: data,\n        Header: nats.Header{\"Nats-Expected-Last-Sequence\": []string{fmt.Sprint(last)}}})\n    if errors.Is(err, nats.ErrWrongLastSequence) { continue } // conflict: re-read and retry\n    break\n}","handlingStrategy":"retry","validationCode":"si, _ := js.StreamInfo(streamName)\nif si.State.LastSeq != expectedLastSeq {\n    // refresh expectedLastSeq before publishing\n}\n","typeGuard":"func isWrongLastSeq(err error) (uint64, bool) {\n    var ae *nats.APIError\n    if errors.As(err, &ae) && ae.ErrorCode() == 10071 {\n        return ae.LastSequence, true\n    }\n    return 0, false\n}\n","tryCatchPattern":"_, err := js.PublishMsg(msg)\nvar ae *nats.APIError\nif errors.As(err, &ae) && ae.ErrorCode() == 10071 {\n    // server returned actual last sequence: resync and retry with backoff\n}\n","preventionTips":["Always source the expected sequence from the previous PubAck, not from a long-lived cache","Use Nats-Msg-Id dedupe when atomicity per message is enough","Add bounded retry with jitter on 10071 conflicts","Watch for stream purges/rollups that reset sequences and invalidate client state"],"tags":["jetstream","publish","concurrency-conflict","expected-last-sequence"],"backgroundTag":"jetstream-last-sequence-mismatch","analyzedSha":"3a66a489d262bf89b71a71c955c94920394532f3","analyzedAt":"2026-09-02T04:41:54.247Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}