HMCL-dev/HMCL · error · JsonParseException
Game directory ID cannot be null
Error message
Game directory ID cannot be null
What it means
GameDirectory's Gson deserializer requires the "id" field of each game-directory entry to deserialize into a non-null GameDirectoryID. When the id is absent or unresolvable, context.deserialize returns null and the adapter throws JsonParseException("Game directory ID cannot be null") instead of producing a GameDirectory with no identity. (JSON-null entries return null early only when the whole element is not an object.)
Solutions
- Add an "id" field with a valid unique ID to the game-directory entry in settings.json
- If migrating from an old format, run the newer HMCL once on the old config via its migration path or re-add the directory in the UI
- Back up and remove the malformed entry so HMCL recreates it with a generated ID
- Check which HMCL version wrote the file and upgrade through intermediate versions rather than hand-editing
Example fix
// before
{ "path": "D:/minecraft", "name": "Main" }
// after
{ "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "path": "D:/minecraft", "name": "Main" } Defensive patterns
Strategy: validation
Validate before calling
for (JsonElement e : settings.getAsJsonArray("gameDirectories")) {
if (e.isJsonObject() && (e.getAsJsonObject().get("id") == null || e.getAsJsonObject().get("id").isJsonNull()))
throw new IOException("Game directory entry missing id");
} Type guard
static boolean hasDirectoryId(JsonObject entry) {
JsonElement id = entry.get("id");
return id != null && !id.isJsonNull();
} Try / catch
try {
GameDirectory d = gson.fromJson(entry, GameDirectory.class);
} catch (JsonParseException e) {
LOGGER.warning("Dropping malformed game directory: " + e.getMessage());
// skip entry and re-add with generated id
} Prevention
- Never hand-edit settings.json entries; use the UI
- When copying entries, always copy or regenerate the id field
- Back up settings.json before manual edits
- Upgrade HMCL through its migration path instead of editing old formats directly
When it happens
Trigger: Deserializing the HMCL settings JSON when a game-directory entry is a JsonObject without an "id" member, or with an "id" value that GameDirectoryID's adapter cannot decode to a non-null ID.
Common situations: Settings file written by an older HMCL version before IDs were introduced; hand-edited settings.json with an entry like {"path": "..."}; corrupted or truncated settings file; migration between versions where the id field was renamed.
Understand the failure class
Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.
Related errors
- Game directory path cannot be null
- Account private data is not an object
- Game directory ID cannot be nil
- Preset ID cannot be nil
- json.toString()
AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10).
Data as JSON: /api/errors/5b112e890db7e7cd.
Report an issue: GitHub.
Appendix: source
Thrown at HMCL/src/main/java/org/jackhuang/hmcl/setting/GameDirectory.java:193
if (name != null && !name.isJsonNull()) {
jsonObject.add("name", name);
}
}
jsonObject.add("path", context.serialize(src.getPath(), PortablePath.class));
if (src.getLegacyGameSettings() != null) {
jsonObject.add("legacyGameSettings", context.serialize(src.getLegacyGameSettings(), GameSettingsPresetID.class));
}
return jsonObject;
}
/// Deserializes a game directory from JSON.
@Override
public @Nullable GameDirectory deserialize(@Nullable JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (!(json instanceof JsonObject obj)) return null;
GameDirectoryID id = context.deserialize(obj.get("id"), GameDirectoryID.class);
if (id == null) {
throw new JsonParseException("Game directory ID cannot be null");
} else if (GameDirectoryID.NIL.equals(id)) {
throw new JsonParseException("Game directory ID cannot be nil");
}
PortablePath path = context.deserialize(obj.get("path"), PortablePath.class);
if (path == null) {
throw new JsonParseException("Game directory path cannot be null");
}
@Nullable LocalizedText name = context.deserialize(obj.get("name"), LocalizedText.class);
return new GameDirectory(id,
name,
path,
context.deserialize(obj.get("legacyGameSettings"), GameSettingsPresetID.class));
}
}
}
View on GitHub (pinned to 24702dc5a0)