HMCL-dev/HMCL · error · JsonParseException
Preset ID cannot be nil
Error message
Preset ID cannot be nil
What it means
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).
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
Example fix
// before
"presets": { "default-skin": { "id": "00000000-0000-0000-0000-000000000000", "gameVersion": "1.20" } }
// after
"presets": { "default-skin": { "id": "4b1f0c3e-9d2a-4f8e-b6a1-3f5c8e7d2a90", "gameVersion": "1.20" } } Defensive patterns
Strategy: validation
Validate before calling
JsonObject preset = ...;
String id = preset.getAsJsonPrimitive("id").getAsString();
if (id.matches("^0{8}(-0{4}){3}-0{12}$"))
throw new IOException("Preset has nil id; assign a fresh unique id"); Type guard
static boolean hasRealPresetId(JsonObject preset) {
JsonElement id = preset.get("id");
return id != null && id.isJsonPrimitive()
&& !"00000000-0000-0000-0000-000000000000".equals(id.getAsString());
} Try / catch
try {
Preset p = new GameSettingsPresetAdapter(gson).deserialize(json, Preset.class, context);
} catch (JsonParseException e) {
if (e.getMessage().contains("nil")) {
LOGGER.warning("Preset with nil id rejected; regenerating id");
// assign a fresh GameSettingsPresetID and retry
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Game directory ID cannot be nil
- Game directory ID cannot be null
- Game directory path cannot be null
- json.toString()
- json.toString()
AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10).
Data as JSON: /api/errors/34d0080f34f30895.
Report an issue: GitHub.
Appendix: source
Thrown at HMCL/src/main/java/org/jackhuang/hmcl/setting/GameSettings.java:246
public SettingProperty<DefaultIsolationType> defaultIsolationTypeProperty() {
return defaultIsolationType;
}
/// JSON adapter for presets.
public static final class Adapter extends ObservableSetting.Adapter<@Nullable Preset> {
@Override
protected Preset createInstance() {
return new Preset();
}
@Override
public @Nullable Preset deserialize(
JsonElement json,
Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
@Nullable Preset result = super.deserialize(json, typeOfT, context);
if (result != null && GameSettingsPresetID.NIL.equals(result.idProperty().getValue())) {
throw new JsonParseException("Preset ID cannot be nil");
}
return result;
}
}
}
/// Reference to a Java runtime selected from HMCL's detected Java list.
///
/// @param version the runtime version reported by the detected Java executable
/// @param pathHash the SHA-256 hash of the normalized Java executable path, or an empty string when unavailable
@NotNullByDefault
public record DetectedJava(String version, String pathHash) {
/// Empty detected Java reference.
public static final DetectedJava EMPTY = new DetectedJava("", "");
/// Creates a detected Java reference.
public DetectedJava {
version = Objects.requireNonNull(version);View on GitHub (pinned to 24702dc5a0)