{"record":{"id":"930f35989d927c24","repo":"HMCL-dev/HMCL","slug":"malformed-response","errorCode":null,"errorMessage":"Malformed response","messagePattern":"Malformed response","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"HMCL/src/main/java/org/jackhuang/hmcl/upgrade/RemoteVersion.java","lineNumber":47,"sourceCode":"import java.util.Optional;\n\npublic record RemoteVersion(UpdateChannel channel, String version, String url, Type type, IntegrityCheck integrityCheck,\n                            boolean preview, boolean force) {\n\n    public static RemoteVersion fetch(UpdateChannel channel, boolean preview, String url) throws IOException {\n        try {\n            JsonObject response = JsonUtils.fromNonNullJson(NetworkUtils.doGet(url), JsonObject.class);\n            String version = Optional.ofNullable(response.get(\"version\")).map(JsonElement::getAsString).orElseThrow(() -> new IOException(\"version is missing\"));\n            String jarUrl = Optional.ofNullable(response.get(\"jar\")).map(JsonElement::getAsString).orElse(null);\n            String jarHash = Optional.ofNullable(response.get(\"jarsha1\")).map(JsonElement::getAsString).orElse(null);\n            boolean force = Optional.ofNullable(response.get(\"force\")).map(JsonElement::getAsBoolean).orElse(false);\n            if (jarUrl != null && jarHash != null) {\n                return new RemoteVersion(channel, version, jarUrl, Type.JAR, new IntegrityCheck(\"SHA-1\", jarHash), preview, force);\n            } else {\n                throw new IOException(\"No download url is available\");\n            }\n        } catch (JsonParseException e) {\n            throw new IOException(\"Malformed response\", e);\n        }\n    }\n\n    @Override\n    public @NotNull String toString() {\n        return \"[\" + version + \" from \" + url + \"]\";\n    }\n\n    public enum Type {\n        JAR\n    }\n}\n","sourceCodeStart":29,"sourceCodeEnd":60,"githubUrl":"https://github.com/HMCL-dev/HMCL/blob/24702dc5a0214034f4c27166d5fd30cad08cec19/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/RemoteVersion.java#L29-L60","documentation":"RemoteVersion.fetch parses the update server's JSON with Gson; if the body is not valid JSON, JsonParseException is caught and rethrown as IOException(\"Malformed response\", e). It means the update endpoint answered, but with content that cannot be parsed as JSON (HTML error page, truncation, wrong content-type).","triggerScenarios":"Checking for updates when the metadata endpoint returns non-JSON content — a captive-portal/HTML error page, a 200 response with garbage body, truncated transfer, or a proxy injecting content.","commonSituations":"Corporate/campus networks with interception proxies; Wi-Fi login portals redirecting requests; CDN or mirror returning an error page with HTTP 200; DNS hijacking; offline-cache corruption.","solutions":["Inspect the chained cause (e.getCause()) message to see the actual Gson parse error","Check network/proxy: disable intercepting proxies or complete captive-portal login, then retry","Verify the update URL responds with valid JSON (curl the endpoint)","Skip the in-app update check and update HMCL manually"],"exampleFix":"// before\n} catch (JsonParseException e) {\n    throw new IOException(\"Malformed response\", e);\n}\n// after\n} catch (JsonParseException | IllegalStateException e) {\n    throw new IOException(\"Malformed response from \" + url\n        + \": \" + StringUtils.truncate(rawBody, 200), e);\n}","handlingStrategy":"try-catch","validationCode":"// validate response body before parsing\nHttpResponse<String> resp = client.send(req, ofString());\nString body = resp.body().trim();\nif (!body.startsWith(\"{\")) {\n    throw new IOException(\"Update endpoint returned non-JSON body (HTTP \" + resp.statusCode() + \")\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    RemoteVersion v = RemoteVersion.fetch(channel, version, preview);\n} catch (IOException e) {\n    if (\"Malformed response\".equals(e.getMessage())) {\n        LOG.warning(\"Update server returned non-JSON; check proxy/network\", e.getCause());\n        skipUpdateCheck();\n    } else throw e;\n}","preventionTips":["Bypass or complete captive-portal/intercepting proxies before checking updates","Validate the update endpoint returns HTTP 200 with a JSON object","Log the raw body (truncated) when parsing fails to aid diagnosis","Fall back to manual update when the metadata endpoint is untrusted"],"tags":["json","network","update","parsing"],"backgroundTag":"invalid-json-response","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"}