{"record":{"id":"bc18704f6fe84777","repo":"nats-io/nats-server","slug":"counter-stream-cannot-use-discard-new","errorCode":null,"errorMessage":"counter stream cannot use discard new","messagePattern":"counter stream cannot use discard new","errorType":"error_code","errorClass":"JSStreamInvalidConfigError","httpStatus":null,"severity":"error","filePath":"server/stream.go","lineNumber":1996,"sourceCode":"\tif cfg.MaxAge != 0 && cfg.Duplicates > cfg.MaxAge {\n\t\treturn StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf(\"duplicates window can not be larger then max age\"))\n\t}\n\tif lim.Duplicates > 0 && cfg.Duplicates > lim.Duplicates {\n\t\treturn StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf(\"duplicates window can not be larger then server limit of %v\",\n\t\t\tlim.Duplicates.String()))\n\t}\n\tif cfg.Duplicates > 0 && cfg.Duplicates < 100*time.Millisecond {\n\t\treturn StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf(\"duplicates window needs to be >= 100ms\"))\n\t}\n\n\tif cfg.DenyPurge && cfg.AllowRollup {\n\t\treturn StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf(\"roll-ups require the purge permission\"))\n\t}\n\n\t// Counter is not compatible with some settings.\n\tif cfg.AllowMsgCounter {\n\t\tif cfg.Discard == DiscardNew {\n\t\t\treturn StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf(\"counter stream cannot use discard new\"))\n\t\t}\n\t\tif cfg.AllowMsgTTL {\n\t\t\treturn StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf(\"counter stream cannot use message TTLs\"))\n\t\t}\n\t\tif cfg.AllowMsgSchedules {\n\t\t\treturn StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf(\"counter stream cannot use message schedules\"))\n\t\t}\n\t\tif cfg.Retention != LimitsPolicy {\n\t\t\treturn StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf(\"counter stream can only use limits retention\"))\n\t\t}\n\t}\n\n\t// Check for new discard new per subject, we require the discard policy to also be new.\n\tif cfg.DiscardNewPer {\n\t\tif cfg.Discard != DiscardNew {\n\t\t\treturn StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf(\"discard new per subject requires discard new policy to be set\"))\n\t\t}\n\t\tif cfg.MaxMsgsPer <= 0 {","sourceCodeStart":1978,"sourceCodeEnd":2014,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/server/stream.go#L1978-L2014","documentation":"A counter stream (AllowMsgCounter=true) tracks per-message counter values and relies on messages never being proactively evicted on publish, so the DiscardNew policy (discard oldest when limits reached) is incompatible. server/stream.go validation rejects Discard == DiscardNew for counter streams. Returned as a JSStreamInvalidConfigError.","triggerScenarios":"AddStream/UpdateStream with AllowMsgCounter=true and Discard=DiscardNew (or DiscardPerSubject defaulting to DiscardNew semantics for counters).","commonSituations":"Building a counter/atomic-increment stream but leaving the common default DiscardNew in place; copying a normal stream config and adding AllowMsgCounter without removing DiscardNew.","solutions":["Set StreamConfig.Discard to DiscardOld when AllowMsgCounter is true.","Or disable AllowMsgCounter if DiscardNew semantics are actually required.","Rely on MaxMsgs/limits only with DiscardOld, or use limits retention with no discard pressure.","Review newer NATS server versions' counter documentation for compatible discard policies."],"exampleFix":"// before\n StreamConfig{Name: \"CTR\", AllowMsgCounter: true, Discard: jetstream.DiscardNew}\n// after\n StreamConfig{Name: \"CTR\", AllowMsgCounter: true, Discard: jetstream.DiscardOld}","handlingStrategy":"validation","validationCode":"if cfg.AllowMsgCounter && cfg.Discard == jetstream.DiscardNew {\n    return fmt.Errorf(\"counter streams require Discard=DiscardOld\")\n}","typeGuard":null,"tryCatchPattern":"var scErr *jetstream.JSApiError\nif _, err := js.CreateStream(ctx, cfg); errors.As(err, &scErr) && strings.Contains(scErr.Description, \"counter stream cannot use discard new\") {\n    cfg.Discard = jetstream.DiscardOld\n    _, err = js.CreateStream(ctx, cfg)\n}","preventionTips":["Whenever adding AllowMsgCounter, set Discard=DiscardOld in the same change.","Keep a validated stream-config constructor function instead of raw literals.","Add a unit test asserting counter configs pass client-side validation."],"tags":["jetstream","stream-config","counter","nats"],"backgroundTag":"invalid-stream-config","analyzedSha":"3a66a489d262bf89b71a71c955c94920394532f3","analyzedAt":"2026-09-02T04:41:54.247Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}