{"record":{"id":"ea6f447488e89c0a","repo":"HMCL-dev/HMCL","slug":"theme-id-is-required-when-a-theme-pack-declares-mu","errorCode":null,"errorMessage":"Theme ID is required when a theme pack declares multiple themes","messagePattern":"Theme ID is required when a theme pack declares multiple themes","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"HMCL/src/main/java/org/jackhuang/hmcl/theme/ThemePackManifest.java","lineNumber":182,"sourceCode":"        ArrayList<Theme> themes = new ArrayList<>(array.size());\n        for (JsonElement item : array) {\n            if (!(item instanceof JsonObject themeObject)) {\n                throw new JsonParseException(\"Theme-pack theme must be an object\");\n            }\n            themes.add(Theme.fromJson(themeObject, true));\n        }\n        return themes;\n    }\n\n    /// Checks that theme IDs and names are present whenever the manifest needs them for disambiguation.\n    private static void checkThemeIdentities(List<Theme> themes) {\n        if (themes.size() <= 1) {\n            return;\n        }\n\n        for (Theme theme : themes) {\n            if (theme.id() == null) {\n                throw new IllegalArgumentException(\"Theme ID is required when a theme pack declares multiple themes\");\n            }\n            if (theme.name() == null) {\n                throw new IllegalArgumentException(\"Theme name is required when a theme pack declares multiple themes\");\n            }\n        }\n    }\n\n    /// Reads a required string member.\n    private static String requireMemberString(JsonObject object, String fieldName) {\n        JsonElement element = object.get(fieldName);\n        if (element == null) {\n            throw new JsonParseException(\"Theme-pack manifest is missing \" + fieldName);\n        }\n        if (!(element instanceof JsonPrimitive primitive) || !primitive.isString()) {\n            throw new JsonParseException(\"Theme-pack manifest field must be a string: \" + fieldName);\n        }\n        return requireNonBlank(primitive.getAsString(), fieldName);\n    }","sourceCodeStart":164,"sourceCodeEnd":200,"githubUrl":"https://github.com/HMCL-dev/HMCL/blob/24702dc5a0214034f4c27166d5fd30cad08cec19/HMCL/src/main/java/org/jackhuang/hmcl/theme/ThemePackManifest.java#L164-L200","documentation":"Thrown by checkThemeIdentities (invoked from the ThemePackManifest compact constructor) when a manifest declares multiple themes but at least one of them lacks an \"id\". With more than one theme, IDs are mandatory so users and the launcher can disambiguate and persist the selected theme.","triggerScenarios":"Constructing ThemePackManifest (or deserializing its JSON) where themes.size() > 1 and any theme has a null id() — e.g. \"themes\": [ { \"name\": \"A\" }, { \"name\": \"B\" } ] without \"id\" fields.","commonSituations":"Author converted a single-theme pack (which allows id-less themes) to multi-theme by duplicating the theme entry but forgetting to add unique IDs; a template copied the single-theme example into a multi-theme array.","solutions":["Add a unique \"id\" (matching the package ID format, e.g. \"com.example.dark\") to every theme in the array.","If the pack really has one unnamed theme, use the singular \"theme\" form or keep the array at exactly one element.","Validate programmatically: parse the manifest and check each theme's id() before distribution."],"exampleFix":"// before\n\"themes\": [\n  { \"name\": \"Dark\" },\n  { \"name\": \"Light\" }\n]\n// after\n\"themes\": [\n  { \"id\": \"dark\", \"name\": \"Dark\" },\n  { \"id\": \"light\", \"name\": \"Light\" }\n]","handlingStrategy":"validation","validationCode":"JsonArray themes = manifestJson.getAsJsonArray(\"themes\");\nif (themes.size() > 1) {\n    for (JsonElement t : themes) {\n        JsonObject theme = t.getAsJsonObject();\n        if (!theme.has(\"id\") || theme.get(\"id\").getAsString().isBlank()) {\n            throw new IllegalArgumentException(\"multi-theme packs require an id per theme\");\n        }\n    }\n}","typeGuard":"static boolean multiThemesAllHaveIds(List<Theme> themes) {\n    return themes.size() <= 1 || themes.stream().allMatch(t -> t.id() != null);\n}","tryCatchPattern":"try {\n    ThemePackManifest pack = gson.fromJson(json, ThemePackManifest.class);\n} catch (IllegalArgumentException e) {\n    // report 'theme missing id' with the pack name to the user\n}","preventionTips":["Give every theme an id as soon as you duplicate a theme entry","Remember ids must match the package ID pattern (letters/digits/dots/dashes)","Use the singular \"theme\" form when the pack has exactly one unnamed theme"],"tags":["theme-pack","manifest","missing-field","validation"],"backgroundTag":"missing-required-config-field","analyzedSha":"24702dc5a0214034f4c27166d5fd30cad08cec19","analyzedAt":"2026-09-10T12:36:46.680Z","contentChangedAt":"2026-09-10T12:36:46.680Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}