{"record":{"id":"9fd1813278200684","repo":"nats-io/nats-server","slug":"10097","errorCode":"10097","errorMessage":"JetStream header size exceeds limits for '%s > %s'","messagePattern":"JetStream header size exceeds limits for '(.+?) > (.+?)'","errorType":"error_code","errorClass":"JSStreamHeaderExceedsMaximumError","httpStatus":400,"severity":"error","filePath":"server/jetstream_batching.go","lineNumber":551,"sourceCode":"\tdiff *batchStagedDiff, mset *stream, subject, rsubject string, hdr []byte, msg []byte, sourced bool, name string,\n\tjsa *jsAccount, allowRollup, denyPurge, allowTTL, allowMsgCounter, allowMsgSchedules bool,\n\tdiscard DiscardPolicy, discardNewPer bool, maxMsgSize int, maxMsgs int64, maxMsgsPer int64, maxBytes int64,\n) ([]byte, []byte, uint64, *ApiError, error) {\n\tvar incr *big.Int\n\tvar hasSchedule bool\n\n\t// Do this before staging any proposal state. All clustered publish paths,\n\t// including atomic and fast batches, use this helper.\n\tif mset.store.Type() == FileStorage && isFileStoreMsgTooLarge(fileStoreMsgSize(subject, hdr, msg)) {\n\t\treturn hdr, msg, 0, NewJSStreamStoreFailedError(ErrMsgTooLarge), ErrMsgTooLarge\n\t}\n\n\t// Some header checks must be checked pre proposal.\n\tif len(hdr) > 0 {\n\t\t// Since we encode header len as u16 make sure we do not exceed.\n\t\t// Again this works if it goes through but better to be pre-emptive.\n\t\tif len(hdr) > math.MaxUint16 {\n\t\t\terr := fmt.Errorf(\"JetStream header size exceeds limits for '%s > %s'\", jsa.acc().Name, name)\n\t\t\treturn hdr, msg, 0, NewJSStreamHeaderExceedsMaximumError(), err\n\t\t}\n\t\t// Counter increments.\n\t\t// Only supported on counter streams, and payload must be empty (if not coming from a source).\n\t\tvar ok bool\n\t\tif incr, ok = getMessageIncr(hdr); !ok {\n\t\t\tapiErr := NewJSMessageIncrInvalidError()\n\t\t\treturn hdr, msg, 0, apiErr, apiErr\n\t\t} else if incr != nil && !sourced {\n\t\t\t// Only do checks if the message isn't sourced. Otherwise, we need to store verbatim.\n\t\t\tif !allowMsgCounter {\n\t\t\t\tapiErr := NewJSMessageIncrDisabledError()\n\t\t\t\treturn hdr, msg, 0, apiErr, apiErr\n\t\t\t} else if len(msg) > 0 {\n\t\t\t\tapiErr := NewJSMessageIncrPayloadError()\n\t\t\t\treturn hdr, msg, 0, apiErr, apiErr\n\t\t\t} else {\n\t\t\t\t// Check for incompatible headers.","sourceCodeStart":533,"sourceCodeEnd":569,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/server/jetstream_batching.go#L533-L569","documentation":"JetStream message header size limit error (JSStreamHeaderExceedsMaximumError, code 10097). NATS encodes header lengths as uint16, so headers must fit in 65535 bytes; batches or counter messages with oversized headers are rejected pre-proposal to avoid downstream corruption.","triggerScenarios":"Publishing a message (or atomic/fast batch entry, including counter increment messages) to a JetStream stream whose headers total more than math.MaxUint16 (65535) bytes.","commonSituations":"Accumulating very large header sets (many Nats-Msg-Id dedup or user headers); batch protocols appending headers per entry until the u16 limit overflows; source/transform pipelines copying headers between messages.","solutions":["Reduce header size: trim or consolidate user headers to stay under 65535 bytes","Move large metadata into the message payload instead of headers","Split oversized atomic/fast batches into smaller batches so per-message headers fit"],"exampleFix":"// before\nhdr := nats.Header{}\nfor i := 0; i < 5000; i++ { hdr.Add(fmt.Sprintf(\"X-Trace-%d\", i), bigValue) }\n// after\nif hdrEncoded := encodeHeaders(hdr); len(hdrEncoded) > math.MaxUint16 {\n\thdr = pruneLargeHeaders(hdr) // keep under 65535 bytes\n}","handlingStrategy":"validation","validationCode":"const maxHeader = math.MaxUint16\nif h := encodedHeaderLen(hdr); h > maxHeader {\n\treturn fmt.Errorf(\"header size %d exceeds %d\", h, maxHeader)\n}","typeGuard":"func headersWithinLimit(hdr nats.Header) bool {\n\treturn encodedHeaderLen(hdr) <= math.MaxUint16\n}","tryCatchPattern":"_, err := js.PublishMsg(msg)\nvar he *nats.APIError\nif errors.As(err, &he) && he.ErrorCode == 10097 {\n\tmsg.Header = pruneHeaders(msg.Header)\n\t_, err = js.PublishMsg(msg)\n}","preventionTips":["Keep per-message headers small; move bulk metadata to payload","Split batches so accumulated headers stay under 65535 bytes","Test pipelines that copy headers between sourced messages"],"tags":["jetstream","headers","limit","batching","nats-server"],"backgroundTag":"header-size-exceeds-maximum","analyzedSha":"3a66a489d262bf89b71a71c955c94920394532f3","analyzedAt":"2026-09-02T04:41:54.247Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}