{"record":{"id":"34d0080f34f30895","repo":"HMCL-dev/HMCL","slug":"preset-id-cannot-be-nil","errorCode":null,"errorMessage":"Preset ID cannot be nil","messagePattern":"Preset ID cannot be nil","errorType":"validation","errorClass":"JsonParseException","httpStatus":null,"severity":"error","filePath":"HMCL/src/main/java/org/jackhuang/hmcl/setting/GameSettings.java","lineNumber":246,"sourceCode":"        public SettingProperty<DefaultIsolationType> defaultIsolationTypeProperty() {\n            return defaultIsolationType;\n        }\n\n        /// JSON adapter for presets.\n        public static final class Adapter extends ObservableSetting.Adapter<@Nullable Preset> {\n            @Override\n            protected Preset createInstance() {\n                return new Preset();\n            }\n\n            @Override\n            public @Nullable Preset deserialize(\n                    JsonElement json,\n                    Type typeOfT,\n                    JsonDeserializationContext context) throws JsonParseException {\n                @Nullable Preset result = super.deserialize(json, typeOfT, context);\n                if (result != null && GameSettingsPresetID.NIL.equals(result.idProperty().getValue())) {\n                    throw new JsonParseException(\"Preset ID cannot be nil\");\n                }\n                return result;\n            }\n        }\n    }\n\n    /// Reference to a Java runtime selected from HMCL's detected Java list.\n    ///\n    /// @param version the runtime version reported by the detected Java executable\n    /// @param pathHash the SHA-256 hash of the normalized Java executable path, or an empty string when unavailable\n    @NotNullByDefault\n    public record DetectedJava(String version, String pathHash) {\n        /// Empty detected Java reference.\n        public static final DetectedJava EMPTY = new DetectedJava(\"\", \"\");\n\n        /// Creates a detected Java reference.\n        public DetectedJava {\n            version = Objects.requireNonNull(version);","sourceCodeStart":228,"sourceCodeEnd":264,"githubUrl":"https://github.com/HMCL-dev/HMCL/blob/24702dc5a0214034f4c27166d5fd30cad08cec19/HMCL/src/main/java/org/jackhuang/hmcl/setting/GameSettings.java#L228-L264","documentation":"GameSettings' Preset deserializer first delegates to the superclass Gson adapter, then rejects any deserialized Preset whose id is the GameSettingsPresetID.NIL sentinel. NIL is reserved for the internal 'no preset' state, so a persisted preset with a NIL id would incorrectly override built-in defaults, and JsonParseException(\"Preset ID cannot be nil\") is thrown. Valid non-object input is tolerated upstream (returns null).","triggerScenarios":"Deserializing the settings JSON preset map when an entry deserializes into a Preset whose idProperty() equals GameSettingsPresetID.NIL — e.g. a preset saved with an uninitialized or placeholder ID.","commonSituations":"Config written by a buggy/older build that persisted presets before assigning an ID; tools templating the config with a nil placeholder ID; manually copied preset entries with the default sentinel id.","solutions":["Replace the preset's nil ID with a fresh unique ID in the settings JSON","Delete the offending preset entry and recreate it in the HMCL UI","Fix the code path that saves presets before a real GameSettingsPresetID is assigned (initialize the id property before persistence)","Restore the settings file from a backup taken before the malformed preset was written"],"exampleFix":"// before\n\"presets\": { \"default-skin\": { \"id\": \"00000000-0000-0000-0000-000000000000\", \"gameVersion\": \"1.20\" } }\n// after\n\"presets\": { \"default-skin\": { \"id\": \"4b1f0c3e-9d2a-4f8e-b6a1-3f5c8e7d2a90\", \"gameVersion\": \"1.20\" } }","handlingStrategy":"validation","validationCode":"JsonObject preset = ...;\nString id = preset.getAsJsonPrimitive(\"id\").getAsString();\nif (id.matches(\"^0{8}(-0{4}){3}-0{12}$\"))\n    throw new IOException(\"Preset has nil id; assign a fresh unique id\");","typeGuard":"static boolean hasRealPresetId(JsonObject preset) {\n    JsonElement id = preset.get(\"id\");\n    return id != null && id.isJsonPrimitive()\n        && !\"00000000-0000-0000-0000-000000000000\".equals(id.getAsString());\n}","tryCatchPattern":"try {\n    Preset p = new GameSettingsPresetAdapter(gson).deserialize(json, Preset.class, context);\n} catch (JsonParseException e) {\n    if (e.getMessage().contains(\"nil\")) {\n        LOGGER.warning(\"Preset with nil id rejected; regenerating id\");\n        // assign a fresh GameSettingsPresetID and retry\n    }\n}","preventionTips":["Initialize preset IDs before saving settings; never persist the NIL sentinel","Filter out built-in/default presets before serializing the preset map","Validate preset IDs (non-nil, unique) when generating configs programmatically","Back up settings before mass edits of the preset section"],"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"}