HMCL-dev/HMCL · error · IOException
"Mod " + modFile + " ` ` is…
Error message
"Mod " + modFile + " `%s` is malformed..".formatted(modToml.getName())
What it means
Thrown by ForgeNewModMetadata.fromFile0 in two cases: the mods.toml file fails TOML parsing (the IOException carries the parse errors as suppressed exceptions), or the file parses but yields no mods table entries. Either way HMCL cannot build LocalModFile metadata from the descriptor.
Solutions
- Inspect the suppressed exceptions on the thrown IOException — they pinpoint the exact TOML syntax error and line.
- Fix the syntax in META-INF/mods.toml (or neoforge.mods.toml) inside the jar and repackage.
- Ensure the descriptor contains a non-empty [[mods]] table with a valid modId.
- Re-download an untouched copy of the mod from its official source.
Example fix
// before (broken mods.toml) [[mods] modId = "example // after [[mods]] modId = "example" version = "1.0.0"
Defensive patterns
Strategy: validation
Validate before calling
TomlParseResult toml = Toml.parse(tomlText);
if (toml.hasErrors()) {
toml.errors().forEach(err -> LOG.warning(err.toString()));
throw new IllegalArgumentException("malformed mods.toml");
} Try / catch
try {
LocalModFile mod = ForgeNewModMetadata.fromFile(modManager, modFile, tree, loaderType);
} catch (IOException e) {
for (Throwable suppressed : e.getSuppressed()) {
LOG.warning("TOML error: " + suppressed);
}
} Prevention
- Read suppressed exceptions on the IOException to find exact TOML syntax errors
- Never hand-edit mods.toml without validating with a TOML linter
- Keep the [[mods]] table non-empty with a valid modId
- Re-download mods rather than repackaging them manually
When it happens
Trigger: 1) Toml.parse reports errors on the contents of mods.toml/neoforge.mods.toml. 2) The parsed ForgeNewModMetadata is null or metadata.getMods() is empty.
Common situations: A mods.toml with a syntax typo (unclosed string, bad table header); a hand-edited descriptor that dropped the [[mods]] block; a descriptor written for a different TOML dialect or corrupted during repackaging.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — 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
- "File " + modFile + " is not a Forge 1.13+ or NeoForge mod."
- "Missing MANIFEST.MF in file " + modFile
- "Invalid metadata file: " + jarInJarMetadata
- Missing embedded mods
- Unknown type of file
AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10).
Data as JSON: /api/errors/d11b2799823dbc5d.
Report an issue: GitHub.
Appendix: source
Thrown at HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/ForgeNewModMetadata.java:230
private static LocalModFile fromFile0(
String tomlPath,
ModLoaderType modLoaderType,
ModManager modManager,
Path modFile,
ZipFileTree tree) throws IOException, JsonParseException {
ZipArchiveEntry modToml = tree.getEntry(tomlPath);
if (modToml == null)
throw new IOException("File " + modFile + " is not a Forge 1.13+ or NeoForge mod.");
TomlParseResult tomlParseResult = Toml.parse(tree.readTextEntry(modToml));
if (tomlParseResult.hasErrors()) {
var ioException = new IOException("Mod " + modFile + " `%s` is malformed..".formatted(modToml.getName()));
tomlParseResult.errors().forEach(ioException::addSuppressed);
throw ioException;
}
ForgeNewModMetadata metadata = JsonUtils.GSON.fromJson(tomlParseResult.toJson(), ForgeNewModMetadata.class);
if (metadata == null || metadata.getMods().isEmpty())
throw new IOException("Mod " + modFile + " `%s` is malformed..".formatted(modToml.getName()));
Mod mod = metadata.getMods().get(0);
ZipArchiveEntry manifestMF = tree.getEntry("META-INF/MANIFEST.MF");
String jarVersion = "";
if (manifestMF != null) {
try (InputStream is = tree.getInputStream(manifestMF)) {
Manifest manifest = new Manifest(is);
jarVersion = manifest.getMainAttributes().getValue(Attributes.Name.IMPLEMENTATION_VERSION);
} catch (IOException e) {
LOG.warning("Failed to parse MANIFEST.MF in file " + modFile);
}
}
ModLoaderType type = analyzeLoader(tomlParseResult, mod.getModId(), modLoaderType);
String logoPath = StringUtils.isNotBlank(mod.getLogoFile()) ? mod.getLogoFile() : metadata.getLogoFile();
return new LocalModFile(modManager, modManager.getLocalMod(mod.getModId(), type), modFile, mod.getDisplayName(), new LocalAddonFile.Description(mod.getDescription()),
mod.getAuthors(), jarVersion == null ? mod.getVersion() : mod.getVersion().replace("${file.jarVersion}", jarVersion), "",View on GitHub (pinned to 24702dc5a0)