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

  1. Replace the preset's nil ID with a fresh unique ID in the settings JSON
  2. Delete the offending preset entry and recreate it in the HMCL UI
  3. Fix the code path that saves presets before a real GameSettingsPresetID is assigned (initialize the id property before persistence)
  4. 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

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


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)