HMCL-dev/HMCL · error · IOException
Malformed response
Error message
Malformed response
What it means
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).
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
Example fix
// before
} catch (JsonParseException e) {
throw new IOException("Malformed response", e);
}
// after
} catch (JsonParseException | IllegalStateException e) {
throw new IOException("Malformed response from " + url
+ ": " + StringUtils.truncate(rawBody, 200), e);
} Defensive patterns
Strategy: try-catch
Validate before calling
// validate response body before parsing
HttpResponse<String> resp = client.send(req, ofString());
String body = resp.body().trim();
if (!body.startsWith("{")) {
throw new IOException("Update endpoint returned non-JSON body (HTTP " + resp.statusCode() + ")");
} Try / catch
try {
RemoteVersion v = RemoteVersion.fetch(channel, version, preview);
} catch (IOException e) {
if ("Malformed response".equals(e.getMessage())) {
LOG.warning("Update server returned non-JSON; check proxy/network", e.getCause());
skipUpdateCheck();
} else throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Illegal result:
- No download url is available
- Malformed response\n" + text
- Metadata response is empty
- GameRemoteVersions.versions cannot be null
AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10).
Data as JSON: /api/errors/930f35989d927c24.
Report an issue: GitHub.
Appendix: source
Thrown at HMCL/src/main/java/org/jackhuang/hmcl/upgrade/RemoteVersion.java:47
import java.util.Optional;
public record RemoteVersion(UpdateChannel channel, String version, String url, Type type, IntegrityCheck integrityCheck,
boolean preview, boolean force) {
public static RemoteVersion fetch(UpdateChannel channel, boolean preview, String url) throws IOException {
try {
JsonObject response = JsonUtils.fromNonNullJson(NetworkUtils.doGet(url), JsonObject.class);
String version = Optional.ofNullable(response.get("version")).map(JsonElement::getAsString).orElseThrow(() -> new IOException("version is missing"));
String jarUrl = Optional.ofNullable(response.get("jar")).map(JsonElement::getAsString).orElse(null);
String jarHash = Optional.ofNullable(response.get("jarsha1")).map(JsonElement::getAsString).orElse(null);
boolean force = Optional.ofNullable(response.get("force")).map(JsonElement::getAsBoolean).orElse(false);
if (jarUrl != null && jarHash != null) {
return new RemoteVersion(channel, version, jarUrl, Type.JAR, new IntegrityCheck("SHA-1", jarHash), preview, force);
} else {
throw new IOException("No download url is available");
}
} catch (JsonParseException e) {
throw new IOException("Malformed response", e);
}
}
@Override
public @NotNull String toString() {
return "[" + version + " from " + url + "]";
}
public enum Type {
JAR
}
}
View on GitHub (pinned to 24702dc5a0)