{"record":{"id":"51b3ceaf8825eb43","repo":"HMCL-dev/HMCL","slug":"game-directory-id-cannot-be-nil","errorCode":null,"errorMessage":"Game directory ID cannot be nil","messagePattern":"Game directory ID cannot be nil","errorType":"validation","errorClass":"JsonParseException","httpStatus":null,"severity":"error","filePath":"HMCL/src/main/java/org/jackhuang/hmcl/setting/GameDirectory.java","lineNumber":195,"sourceCode":"                }\n            }\n            jsonObject.add(\"path\", context.serialize(src.getPath(), PortablePath.class));\n            if (src.getLegacyGameSettings() != null) {\n                jsonObject.add(\"legacyGameSettings\", context.serialize(src.getLegacyGameSettings(), GameSettingsPresetID.class));\n            }\n\n            return jsonObject;\n        }\n\n        /// Deserializes a game directory from JSON.\n        @Override\n        public @Nullable GameDirectory deserialize(@Nullable JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {\n            if (!(json instanceof JsonObject obj)) return null;\n            GameDirectoryID id = context.deserialize(obj.get(\"id\"), GameDirectoryID.class);\n            if (id == null) {\n                throw new JsonParseException(\"Game directory ID cannot be null\");\n            } else if (GameDirectoryID.NIL.equals(id)) {\n                throw new JsonParseException(\"Game directory ID cannot be nil\");\n            }\n            PortablePath path = context.deserialize(obj.get(\"path\"), PortablePath.class);\n            if (path == null) {\n                throw new JsonParseException(\"Game directory path cannot be null\");\n            }\n            @Nullable LocalizedText name = context.deserialize(obj.get(\"name\"), LocalizedText.class);\n\n            return new GameDirectory(id,\n                    name,\n                    path,\n                    context.deserialize(obj.get(\"legacyGameSettings\"), GameSettingsPresetID.class));\n        }\n\n    }\n}\n","sourceCodeStart":177,"sourceCodeEnd":211,"githubUrl":"https://github.com/HMCL-dev/HMCL/blob/24702dc5a0214034f4c27166d5fd30cad08cec19/HMCL/src/main/java/org/jackhuang/hmcl/setting/GameDirectory.java#L177-L211","documentation":"GameDirectory's deserializer rejects entries whose \"id\" decodes to the special GameDirectoryID.NIL sentinel (e.g. an all-zero/placeholder UUID). NIL is reserved for internal/default use, so a persisted directory with NIL has no meaningful identity and JsonParseException(\"Game directory ID cannot be nil\") is thrown. Note the whole element not being an object yields null earlier — this error fires only for object entries with a NIL id.","triggerScenarios":"Deserializing a settings JSON entry where the \"id\" field deserializes into GameDirectoryID.NIL — e.g. an id serialized as all zeros/empty placeholder because the ID was never initialized before saving.","commonSituations":"A tool or old build that wrote a default/zero UUID for new directories; config generated by templating that left the id as a placeholder; saving before the ID property was initialized due to a version bug.","solutions":["Replace the nil/zero ID with a fresh unique ID (e.g. a random UUID) in settings.json","Re-add the game directory through the HMCL UI, which generates a valid ID","Find the code path that saved GameDirectoryID.NIL (uninitialized ID property) and ensure a real ID is assigned before persistence","Delete the offending entry and let HMCL recreate it"],"exampleFix":"// before\n{ \"id\": \"00000000-0000-0000-0000-000000000000\", \"path\": \"D:/minecraft\" }\n// after\n{ \"id\": \"7c9e6679-7425-40de-944b-e07fc1f90ae7\", \"path\": \"D:/minecraft\" }","handlingStrategy":"validation","validationCode":"JsonObject entry = ...;\nString id = entry.getAsJsonPrimitive(\"id\").getAsString();\nif (id.matches(\"^0{8}(-0{4}){3}-0{12}$\"))\n    throw new IOException(\"Game directory has nil id; assign a real unique id\");","typeGuard":"static boolean hasRealId(JsonObject entry) {\n    JsonElement id = entry.get(\"id\");\n    return id != null && id.isJsonPrimitive()\n        && !\"00000000-0000-0000-0000-000000000000\".equals(id.getAsString());\n}","tryCatchPattern":"try {\n    GameDirectory d = gson.fromJson(entry, GameDirectory.class);\n} catch (JsonParseException e) {\n    if (e.getMessage().contains(\"nil\")) {\n        LOGGER.warning(\"Preset/directory had placeholder id; regenerating\");\n        // replace id with a fresh UUID and retry\n    }\n}","preventionTips":["Assign real unique IDs at creation time before persisting","Never serialize sentinel/nil IDs to config files","Regenerate placeholder IDs left by templates or tooling","Validate ids (non-nil, unique) before saving settings"],"tags":["json","deserialization","settings","sentinel-value"],"backgroundTag":"invalid-identifier","analyzedSha":"24702dc5a0214034f4c27166d5fd30cad08cec19","analyzedAt":"2026-09-10T12:36:46.680Z","contentChangedAt":"2026-09-10T12:36:46.680Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}