{"record":{"id":"d19d0effca99b8e9","repo":"nats-io/nats-server","slug":"duplicates-window-can-not-be-larger-then-max-age","errorCode":null,"errorMessage":"duplicates window can not be larger then max age","messagePattern":"duplicates window can not be larger then max age","errorType":"error_code","errorClass":"JSStreamInvalidConfigError","httpStatus":null,"severity":"error","filePath":"server/stream.go","lineNumber":1979,"sourceCode":"\t\t\t\treturn StreamConfig{}, NewJSPedanticError(fmt.Errorf(\"duplicate window limits are higher than current limits\"))\n\t\t\t}\n\t\t\tmaxWindow = lim.Duplicates\n\t\t}\n\t\tif cfg.MaxAge != 0 && cfg.MaxAge < maxWindow {\n\t\t\tif pedantic {\n\t\t\t\treturn StreamConfig{}, NewJSPedanticError(fmt.Errorf(\"duplicate window cannot be bigger than max age\"))\n\t\t\t}\n\t\t\tcfg.Duplicates = cfg.MaxAge\n\t\t} else {\n\t\t\tcfg.Duplicates = maxWindow\n\t\t}\n\t}\n\tif cfg.Duplicates < 0 {\n\t\treturn StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf(\"duplicates window can not be negative\"))\n\t}\n\t// Check that duplicates is not larger then age if set.\n\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}","sourceCodeStart":1961,"sourceCodeEnd":1997,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/server/stream.go#L1961-L1997","documentation":"JetStream requires the Duplicates window (duplicate message cache TTL) to be no larger than the stream's MaxAge, otherwise a message could be considered a duplicate after it was already expired. StreamConfig validation in server/stream.go rejects Duplicates > MaxAge when MaxAge is set. Returned as a JSStreamInvalidConfigError.","triggerScenarios":"AddStream/UpdateStream with MaxAge set (non-zero) and Duplicates greater than MaxAge, e.g. Duplicates: 2*time.Minute, MaxAge: time.Minute.","commonSituations":"Setting a short retention (MaxAge) for ephemeral data while keeping the default 2-minute duplicates window; tuning MaxAge down later without lowering Duplicates; template/accounts defaults where Duplicates is inherited implicitly.","solutions":["Lower StreamConfig.Duplicates to be <= MaxAge.","Or raise MaxAge so it is >= the desired Duplicates window.","Set Duplicates to 0 to let the server pick a compliant default.","On stream update, adjust both values in the same API call so validation passes."],"exampleFix":"// before\n StreamConfig{Name: \"EVENTS\", MaxAge: time.Minute, Duplicates: 2 * time.Minute}\n// after\n StreamConfig{Name: \"EVENTS\", MaxAge: time.Minute, Duplicates: time.Minute}","handlingStrategy":"validation","validationCode":"if cfg.MaxAge != 0 && cfg.Duplicates > cfg.MaxAge {\n    return fmt.Errorf(\"duplicates (%s) must be <= max age (%s)\", cfg.Duplicates, cfg.MaxAge)\n}","typeGuard":null,"tryCatchPattern":"var scErr *jetstream.JSApiError\nif _, err := js.UpdateStream(ctx, cfg); errors.As(err, &scErr) && strings.Contains(scErr.Description, \"duplicates window can not be larger then max age\") {\n    cfg.Duplicates = cfg.MaxAge\n    _, err = js.UpdateStream(ctx, cfg)\n}","preventionTips":["Change MaxAge and Duplicates together in the same update call.","When lowering MaxAge, first verify the current Duplicates window fits.","Default to Duplicates=0 so the server derives a compliant value."],"tags":["jetstream","stream-config","validation","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"}