HMCL-dev/HMCL · error · IOException
"Mod " + modFile + " `mcmod.info` is malformed"
Error message
"Mod " + modFile + " `mcmod.info` is malformed"
What it means
ForgeOldModMetadata.fromFile parses a legacy Forge mod's `mcmod.info` file. When the top-level JSON object deserializes to null (e.g. the file contains `null` or an unparseable-but-lexed object), an IOException with this message is thrown because no mod metadata could be extracted.
Solutions
- Open the mod jar and verify `mcmod.info` contains a valid object with a non-empty `modList` array
- Re-download or rebuild the mod from a trusted source
- Remove the offending jar from the mods folder if it is not a real Forge mod
Example fix
// before (malformed mcmod.info)
null
// after
{
"modList": [{ "modid": "example", "name": "Example", "version": "1.0" }]
} Defensive patterns
Strategy: try-catch
Validate before calling
try (var z = new ZipFile(jar.toFile())) {
var e = z.getEntry("mcmod.info");
if (e == null) throw new IllegalStateException("no mcmod.info");
String s = new String(z.getInputStream(e).readAllBytes(), StandardCharsets.UTF_8);
if (s.isBlank() || s.trim().equals("null")) throw new IllegalStateException("blank mcmod.info");
} Try / catch
try {
LocalModFile f = ForgeOldModMetadata.fromFile(modManager, modFile, tree);
} catch (IOException | JsonParseException e) {
log.warn("Skipping mod with malformed mcmod.info: " + modFile, e);
} Prevention
- Never hand-edit mcmod.info; let the build plugin generate it
- Verify jar integrity after download
- Wrap per-mod metadata parsing so one bad jar doesn't abort a batch scan
When it happens
Trigger: Calling ForgeOldModMetadata.fromFile on a jar whose `mcmod.info` first token is BEGIN_OBJECT but GSON's fromJson yields a null ForgeOldModMetadataLst (literal `null` content or empty/blank payload parsed leniently).
Common situations: Manually edited or truncated `mcmod.info` files inside old Forge jars; mods packaged by old build tools writing an empty metadata file; corrupted downloads.
Understand the failure class
Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- "Unexpected first token: " + firstToken
- "File " + modFile + " is not a LiteLoader mod."
- "Mod " + modFile + " `litemod.json` is malformed."
- "File " + modFile + " is not a Quilt mod."
- Invalid theme-pack manifest
AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10).
Data as JSON: /api/errors/3665b23050c3f82d.
Report an issue: GitHub.
Appendix: source
Thrown at HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/ForgeOldModMetadata.java:143
}
public static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFileTree tree) throws IOException, JsonParseException {
ZipArchiveEntry mcmod = tree.getEntry("mcmod.info");
if (mcmod == null)
throw new IOException("File " + modFile + " is not a Forge mod.");
List<ForgeOldModMetadata> modList;
try (var reader = tree.getBufferedReader(mcmod);
var jsonReader = new JsonReader(reader)) {
JsonToken firstToken = jsonReader.peek();
if (firstToken == JsonToken.BEGIN_ARRAY)
modList = JsonUtils.GSON.fromJson(jsonReader, listTypeOf(ForgeOldModMetadata.class));
else if (firstToken == JsonToken.BEGIN_OBJECT) {
ForgeOldModMetadataLst list = JsonUtils.GSON.fromJson(jsonReader, ForgeOldModMetadataLst.class);
if (list == null)
throw new IOException("Mod " + modFile + " `mcmod.info` is malformed");
modList = list.modList();
} else {
throw new JsonParseException("Unexpected first token: " + firstToken);
}
}
if (modList == null || modList.isEmpty())
throw new IOException("Mod " + modFile + " `mcmod.info` is malformed");
ForgeOldModMetadata metadata = modList.get(0);
String authors = metadata.getAuthor();
if (StringUtils.isBlank(authors) && metadata.getAuthors().length > 0)
authors = String.join(", ", metadata.getAuthors());
if (StringUtils.isBlank(authors) && metadata.getAuthorList().length > 0)
authors = String.join(", ", metadata.getAuthorList());
if (StringUtils.isBlank(authors))
authors = metadata.getCredits();
return new LocalModFile(modManager, modManager.getLocalMod(metadata.getModId(), ModLoaderType.FORGE), modFile, metadata.getName(), new LocalAddonFile.Description(metadata.getDescription()),
authors, metadata.getVersion(), metadata.getGameVersion(),View on GitHub (pinned to 24702dc5a0)