{"record":{"id":"f9d856389b58da4b","repo":"microsoft/garnet","slug":"unsupported-object-type-id-0x-type-x2-the-data-m","errorCode":null,"errorMessage":"Unsupported object type id 0x{type:X2}; the data may have been written by an incompatible Garnet version (e.g. legacy custom objects that used a different type-id range).","messagePattern":"Unsupported object type id 0x(.+?); the data may have been written by an incompatible Garnet version \\(e\\.g\\. legacy custom objects that used a different type-id range\\)\\.","errorType":"exception","errorClass":"GarnetException","httpStatus":null,"severity":"critical","filePath":"libs/server/Objects/Types/GarnetObjectSerializer.cs","lineNumber":84,"sourceCode":"                GarnetObjectType.SortedSet => new SortedSetObject(binaryReader),\n                GarnetObjectType.List => new ListObject(binaryReader),\n                GarnetObjectType.Hash => new HashObject(binaryReader),\n                GarnetObjectType.Set => new SetObject(binaryReader),\n                _ => CustomDeserialize((byte)type, binaryReader),\n            };\n            return obj;\n        }\n\n        private IGarnetObject CustomDeserialize(byte type, BinaryReader binaryReader)\n        {\n            // Built-in type ids (0..LastObjectType) are handled by the caller. A type id below the\n            // fixed custom-object base therefore lies in the reserved built-in band and is not valid\n            // for this build: it was written either by an older build whose custom objects were based\n            // at LastObjectType+1 (rather than the fixed base), or by a newer build with additional\n            // built-in types. Fail fast instead of silently returning null, which would drop the\n            // record and lose data without any indication.\n            if (type < CustomCommandManager.CustomTypeIdStartOffset)\n                throw new GarnetException($\"Unsupported object type id 0x{type:X2}; the data may have been written by an incompatible Garnet version (e.g. legacy custom objects that used a different type-id range).\");\n\n            if (!customCommandManager.TryGetCustomObjectCommand(type, out var cmd)) return null;\n            return cmd.factory.Deserialize(type, binaryReader);\n        }\n\n        /// <inheritdoc />\n        public override void Serialize(IGarnetObject obj) => SerializeInternal(base.writer, obj);\n\n        /// <summary>Thread safe version of Serialize.</summary>\n        /// <param name=\"obj\"></param>\n        /// <returns></returns>\n        public static void Serialize(IGarnetObject obj, out byte[] bytes)\n        {\n            Debug.Assert(obj != null);\n\n            using var ms = new MemoryStream();\n            using var binaryWriter = new BinaryWriter(ms, Encoding.UTF8);\n            SerializeInternal(binaryWriter, obj);","sourceCodeStart":66,"sourceCodeEnd":102,"githubUrl":"https://github.com/microsoft/garnet/blob/951b0fc6838721f89d102c2bbe1b914e8d39d700/libs/server/Objects/Types/GarnetObjectSerializer.cs#L66-L102","documentation":"Thrown by CustomDeserialize when a custom object's persisted type id falls below CustomCommandManager.CustomTypeIdStartOffset - i.e., in the reserved built-in band - yet is not a recognized built-in type. Per the code comments, this happens with legacy custom objects whose ids were based at LastObjectType+1 (older scheme) instead of the current fixed base, or with a build whose built-in/custom bands differ. Failing fast prevents silent data loss (returning null would drop the record).","triggerScenarios":"Deserializing a persisted custom object whose type id is in the reserved built-in band because it was written by an older Garnet (legacy custom-id scheme) or an incompatible build with a different band layout.","commonSituations":"Upgrading Garnet across the version that changed custom-object id basing; loading data written by a much older build; mixing builds with different CustomTypeIdStartOffset.","solutions":["Identify which build wrote the data and which is reading it; align them.","Migrate legacy custom-object data via RESP-level export/import rather than reusing raw object files.","Ensure the same modules (and thus the same custom-id assignments) are loaded on the reading node."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Reject persisted custom type ids outside the valid custom band before deserializing\nif (type < CustomCommandManager.CustomTypeIdStartOffset)\n    throw new InvalidOperationException($\"Refusing custom object with out-of-band type id 0x{type:X2}.\");","typeGuard":null,"tryCatchPattern":"catch (GarnetException ex) when (ex.Message.Contains(\"Unsupported object type id\")) {\n    logger.LogCritical(\"Legacy/incompatible custom-object id encountered; migrate data via RESP export/import.\");\n    throw;\n}","preventionTips":["Treat custom-object id bands as a persisted contract; do not change the base across versions.","Load the same set of modules in the same order on all nodes sharing data."],"tags":["serialization","version-mismatch","custom-objects","persistence"],"backgroundTag":null,"analyzedSha":"951b0fc6838721f89d102c2bbe1b914e8d39d700","analyzedAt":"2026-08-13T19:01:32.939Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}