{"record":{"id":"94db081c024c5613","repo":"HMCL-dev/HMCL","slug":"metadata-response-is-empty","errorCode":null,"errorMessage":"Metadata response is empty","messagePattern":"Metadata response is empty","errorType":"exception","errorClass":"JsonParseException","httpStatus":null,"severity":"error","filePath":"HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorServer.java","lineNumber":168,"sourceCode":"    }\n\n    private void refreshMetadata(String text) throws IOException {\n        long timestamp = System.currentTimeMillis();\n        try {\n            setMetadataResponse(text, timestamp);\n        } catch (JsonParseException e) {\n            throw new IOException(\"Malformed response\\n\" + text, e);\n        }\n\n        metadataRefreshed = true;\n        LOG.info(\"authlib-injector server metadata refreshed: \" + url);\n        Platform.runLater(helper::invalidate);\n    }\n\n    private void setMetadataResponse(String metadataResponse, long metadataTimestamp) throws JsonParseException {\n        JsonObject response = GSON.fromJson(metadataResponse, JsonObject.class);\n        if (response == null) {\n            throw new JsonParseException(\"Metadata response is empty\");\n        }\n\n        synchronized (this) {\n            this.metadataResponse = metadataResponse;\n            this.metadataTimestamp = metadataTimestamp;\n\n            Optional<JsonObject> metaObject = tryCast(response.get(\"meta\"), JsonObject.class);\n\n            this.name = metaObject.flatMap(meta -> tryCast(meta.get(\"serverName\"), JsonPrimitive.class).map(JsonPrimitive::getAsString))\n                    .orElse(null);\n            this.links = metaObject.flatMap(meta -> tryCast(meta.get(\"links\"), JsonObject.class))\n                    .map(linksObject -> {\n                        Map<String, String> converted = new LinkedHashMap<>();\n                        linksObject.entrySet().forEach(\n                                entry -> tryCast(entry.getValue(), JsonPrimitive.class).ifPresent(element -> {\n                                    converted.put(entry.getKey(), element.getAsString());\n                                }));\n                        return converted;","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/HMCL-dev/HMCL/blob/24702dc5a0214034f4c27166d5fd30cad08cec19/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorServer.java#L150-L186","documentation":"AuthlibInjectorServer.setMetadataResponse parses the cached or freshly fetched authlib-injector metadata JSON into a JsonObject. If GSON.fromJson returns null — meaning the stored string is null, empty, or the literal 'null' — the server cannot be configured, so a JsonParseException is thrown to abort metadata application.","triggerScenarios":"refreshMetadata downloads metadata and the server returns an empty body (or 200 with no content); restoreMetadataCache loads a persisted metadata cache file that is empty, null, or contains the string \"null\"; a corrupt/zero-byte cache is read from disk.","commonSituations":"The authlib-injector server is misconfigured or behind a broken reverse proxy returning empty 200 responses; the local metadata cache file was truncated by a crash or disk-full condition; the metadata URL points at a plain-text endpoint rather than the JSON metadata root.","solutions":["Check that GET <server-url>/ returns valid JSON metadata (try curl and inspect the body)","Delete the corrupted cached metadata for this server in HMCL's account data and refresh metadata again","Verify the server URL is the authlib-injector API root (not a webpage); re-add the server in HMCL","If you are the server operator, fix the reverse proxy/backend so it returns the metadata JSON"],"exampleFix":"// before: trusting whatever was cached\nserver.restoreMetadataCache();\n// after: validate before use\nString cached = readMetadataCacheFile();\nif (cached == null || cached.isBlank() || \"null\".equals(cached.trim())) {\n    server.refreshMetadata(); // force re-download instead of restoring empty cache\n} else {\n    server.restoreMetadataCache();\n}","handlingStrategy":"validation","validationCode":"String meta = readCachedMetadata();\nif (meta == null || meta.isBlank() || \"null\".equals(meta.trim())) {\n    refreshMetadata(); // re-download instead of restoring empty cache\n} else if (!meta.trim().startsWith(\"{\")) {\n    throw new IllegalArgumentException(\"Cached metadata is not JSON\");\n}","typeGuard":"boolean isValidMetadata(String s) {\n    return s != null && !s.isBlank() && !\"null\".equals(s.trim()) && s.trim().startsWith(\"{\");\n}","tryCatchPattern":"try {\n    server.restoreMetadataCache();\n} catch (JsonParseException e) {\n    if (e.getMessage().contains(\"Metadata response is empty\")) {\n        server.refreshMetadata(); // fall back to network fetch\n    } else throw e;\n}","preventionTips":["Validate cached metadata is non-empty JSON before restore","Delete cache files on parse failure so they are re-downloaded","Verify the server URL returns JSON with curl before adding it"],"tags":["json","metadata","network","empty-response"],"backgroundTag":"empty-response-body","analyzedSha":"24702dc5a0214034f4c27166d5fd30cad08cec19","analyzedAt":"2026-09-10T12:36:46.680Z","contentChangedAt":"2026-09-10T12:36:46.680Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}