{"record":{"id":"03910ef2a049b773","repo":"HMCL-dev/HMCL","slug":"theme-name-is-required-when-a-theme-pack-declares","errorCode":null,"errorMessage":"Theme name is required when a theme pack declares multiple themes","messagePattern":"Theme name 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":185,"sourceCode":"                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    }\n\n    /// Parses a localized text value.\n    static LocalizedText parseLocalizedText(JsonElement element, String field) {","sourceCodeStart":167,"sourceCodeEnd":203,"githubUrl":"https://github.com/HMCL-dev/HMCL/blob/24702dc5a0214034f4c27166d5fd30cad08cec19/HMCL/src/main/java/org/jackhuang/hmcl/theme/ThemePackManifest.java#L167-L203","documentation":"Thrown by checkThemeIdentities (invoked from the ThemePackManifest compact constructor) when a manifest declares multiple themes but at least one of them lacks a \"name\". Names are required alongside IDs in multi-theme packs so the UI can present a human-readable label for each selectable theme.","triggerScenarios":"Constructing ThemePackManifest (or deserializing its JSON) where themes.size() > 1 and any theme has a null name() — e.g. \"themes\": [ { \"id\": \"dark\" }, { \"id\": \"light\", \"name\": \"Light\" } ].","commonSituations":"Author added IDs (fixing the ID error) but omitted display names; a generated manifest wrote id-only theme stubs; a localization file was expected to supply names at runtime but the manifest itself never declared them.","solutions":["Add a non-blank \"name\" (string or localized object) to every theme in the \"themes\" array.","Ensure every theme has BOTH id and name when the pack declares more than one theme.","Test-load the pack before release to surface this IllegalArgumentException early."],"exampleFix":"// before\n\"themes\": [\n  { \"id\": \"dark\" },\n  { \"id\": \"light\", \"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(\"name\")) {\n            throw new IllegalArgumentException(\"multi-theme packs require a name per theme\");\n        }\n    }\n}","typeGuard":"static boolean multiThemesAllHaveNames(List<Theme> themes) {\n    return themes.size() <= 1 || themes.stream().allMatch(t -> t.name() != null);\n}","tryCatchPattern":"try {\n    ThemePackManifest pack = gson.fromJson(json, ThemePackManifest.class);\n} catch (IllegalArgumentException e) {\n    // report 'theme missing name' with the pack name to the user\n}","preventionTips":["Always pair id with a display name when adding a theme","Names accept a plain string or a locale-to-string object","Load-test every multi-theme pack before release"],"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"}