{"record":{"id":"ef776b2407574f08","repo":"nats-io/nats-server","slug":"storage-type-can-not-be-updated","errorCode":null,"errorMessage":"storage type can not be updated","messagePattern":"storage type can not be updated","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/consumer.go","lineNumber":2538,"sourceCode":"\t\t} else {\n\t\t\tburst = int(acc.srv.getOpts().MaxPayload)\n\t\t}\n\t}\n\n\to.rlimit = rate.NewLimiter(rl, burst)\n}\n\n// Check if new consumer config allowed vs old.\nfunc (acc *Account) checkNewConsumerConfig(cfg, ncfg *ConsumerConfig) error {\n\tif reflect.DeepEqual(cfg, ncfg) {\n\t\treturn nil\n\t}\n\t// Something different, so check since we only allow certain things to be updated.\n\tif cfg.DeliverPolicy != ncfg.DeliverPolicy {\n\t\treturn errors.New(\"deliver policy can not be updated\")\n\t}\n\tif cfg.MemoryStorage != ncfg.MemoryStorage {\n\t\treturn errors.New(\"storage type can not be updated\")\n\t}\n\t// Direct and Sourcing classify the consumer for its whole lifetime, which the\n\t// stream relies on when walking its consumer list, so they can not change.\n\tif cfg.Direct != ncfg.Direct {\n\t\treturn errors.New(\"direct can not be updated\")\n\t}\n\tif cfg.Sourcing != ncfg.Sourcing {\n\t\treturn errors.New(\"sourcing can not be updated\")\n\t}\n\tif cfg.OptStartSeq != ncfg.OptStartSeq {\n\t\treturn errors.New(\"start sequence can not be updated\")\n\t}\n\tif cfg.OptStartTime != nil && ncfg.OptStartTime != nil {\n\t\t// Both have start times set, compare them directly:\n\t\tif !cfg.OptStartTime.Equal(*ncfg.OptStartTime) {\n\t\t\treturn errors.New(\"start time can not be updated\")\n\t\t}\n\t} else if cfg.OptStartTime != nil || ncfg.OptStartTime != nil {","sourceCodeStart":2520,"sourceCodeEnd":2556,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/server/consumer.go#L2520-L2556","documentation":"A consumer update tried to change MemoryStorage, i.e. switch the consumer between memory and file storage. Storage type is fixed at creation, so checkNewConsumerConfig rejects any difference. The request fails with this error.","triggerScenarios":"js.UpdateConsumer (or CONSUMER.CREATE on an existing durable) where cfg.MemoryStorage differs from ncfg.MemoryStorage — e.g. created with FileStorage but update passes MemoryStorage: true (or vice versa).","commonSituations":"Switching consumers to memory for performance after rollout; environment-specific templates toggling storage; recreating a durable from an infra-as-code diff that changed the storage flag.","solutions":["Delete and recreate the consumer with the desired storage type.","Keep the existing storage type in the update payload.","Use a new durable name with the desired storage and cut over.","Standardize storage settings in templates so env-specific diffs don't flip this field."],"exampleFix":"// before\njs.UpdateConsumer(stream, &nats.ConsumerConfig{Durable: \"worker\", MemoryStorage: true})\n// after\njs.DeleteConsumer(stream, \"worker\")\njs.AddConsumer(stream, &nats.ConsumerConfig{Durable: \"worker\", MemoryStorage: true})","handlingStrategy":"validation","validationCode":"if existing.MemoryStorage != desired.MemoryStorage {\n\treturn errors.New(\"storage type cannot be updated; recreate the consumer\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Pin storage type centrally, not per environment template","Echo the existing MemoryStorage value back in update payloads","Recreate consumers when storage requirements change"],"tags":["jetstream","consumer","storage","immutable-config"],"backgroundTag":"jetstream-consumer-field-immutable","analyzedSha":"3a66a489d262bf89b71a71c955c94920394532f3","analyzedAt":"2026-09-02T04:41:54.247Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}