{"record":{"id":"fc031395d5b71701","repo":"nats-io/nats-server","slug":"js-stream-wrong-last-sequence","errorCode":"JS_STREAM_WRONG_LAST_SEQUENCE","errorMessage":"last sequence by subject mismatch","messagePattern":"last sequence by subject mismatch","errorType":"error_code","errorClass":"JSStreamWrongLastSequenceConstantError","httpStatus":null,"severity":"error","filePath":"server/jetstream_batching.go","lineNumber":770,"sourceCode":"\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 {\n\t\t\t\terr := errors.New(\"last sequence by subject mismatch\")\n\t\t\t\treturn hdr, msg, 0, NewJSStreamWrongLastSequenceConstantError(), err\n\t\t\t}\n\n\t\t\t// If the subject is already in process, block as otherwise we could have\n\t\t\t// multiple messages inflight with the same subject.\n\t\t\tif _, found := mset.expectedPerSubjectInProcess[seqSubj]; found {\n\t\t\t\terr := errors.New(\"last sequence by subject mismatch\")\n\t\t\t\treturn hdr, msg, 0, NewJSStreamWrongLastSequenceConstantError(), err\n\t\t\t}\n\n\t\t\t// If the subject is already in process but without expected headers, block as we would have\n\t\t\t// multiple messages inflight with the same subject.\n\t\t\tif _, ok := mset.inflight[seqSubj]; ok {\n\t\t\t\terr := errors.New(\"last sequence by subject mismatch\")\n\t\t\t\treturn hdr, msg, 0, NewJSStreamWrongLastSequenceConstantError(), err\n\t\t\t}\n\n\t\t\t// If we've already done an expected-check on this subject, use the cached result.","sourceCodeStart":752,"sourceCodeEnd":788,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/server/jetstream_batching.go#L752-L788","documentation":"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').","triggerScenarios":"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.","commonSituations":"Counter-style workloads publishing duplicate keyed expected-sequence messages in a single batch; concurrent publishers racing on the same subject within one batch context.","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."],"exampleFix":"// before: two same-subject expected checks in one batch\nbatch.Publish(\"counters.a\", nil, nats.ExpectedLastSeqPerSubject(10))\nbatch.Publish(\"counters.a\", nil, nats.ExpectedLastSeqPerSubject(11)) // mismatch: subject inflight\n// after: commit the first write before the next check\nbatch.Publish(\"counters.a\", nil, nats.ExpectedLastSeqPerSubject(10))\nbatch.Flush()\njs.Publish(\"counters.a\", nil, nats.ExpectedLastSeqPerSubject(11))","handlingStrategy":"validation","validationCode":"// reject same-subject duplicates carrying expected-sequence headers before adding to a batch\nseen := map[string]struct{}{}\nfunc canBatch(subj string, hasExpectedHeader bool) bool {\n\tif hasExpectedHeader {\n\t\tif _, dup := seen[subj]; dup {\n\t\t\treturn false // would be rejected: subject already inflight in batch\n\t\t}\n\t}\n\tseen[subj] = struct{}{}\n\treturn true\n}","typeGuard":null,"tryCatchPattern":"err := batch.Flush()\nvar apiErr *nats.APIError\nif errors.As(err, &apiErr) && apiErr.ErrorCode == nats.JSStreamWrongLastSequence {\n\tjs.Publish(subj, data, nats.ExpectedLastSeqPerSubject(seq)) // resend alone after commit\n}","preventionTips":["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."],"tags":["jetstream","batching","expected-sequence","concurrency"],"backgroundTag":"wrong-last-sequence-by-subject","analyzedSha":"3a66a489d262bf89b71a71c955c94920394532f3","analyzedAt":"2026-09-02T04:41:54.247Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}