HMCL-dev/HMCL · error · IOException

"File " + modFile + " is not a Forge 1.13+ or NeoForge mod."

Error message

"File " + modFile + " is not a Forge 1.13+ or NeoForge mod."

What it means

Thrown by ForgeNewModMetadata.fromFile as the last-resort failure after fromEmbeddedMod (which handles Forge 1.13+/NeoForge jars with META-INF/MANIFEST.MF or jar-in-jar data) also raised an exception. It means the jar neither exposes the expected mods.toml/neoforge.mods.toml nor embeds the manifest data HMCL needs, so it cannot be recognized as a Forge 1.13+/NeoForge mod.

Solutions

  1. Check the jar for META-INF/mods.toml or META-INF/neoforge.mods.toml (unzip -l mod.jar); if absent, it is not a 1.13+ Forge/NeoForge mod.
  2. For 1.12.2-and-older mods, use ForgeOldModMetadata.fromFile instead.
  3. Re-download the mod; a truncated or corrupt jar fails both parse paths.
  4. Confirm the mod is actually a Forge/NeoForge build, not a Fabric build, before calling this parser.

Example fix

// before
LocalModFile mod = ForgeNewModMetadata.fromFile(modManager, modFile, tree, ModLoaderType.FORGE); // throws on 1.12 mods
// after
if (tree.getEntry("META-INF/mods.toml") == null && tree.getEntry("META-INF/MANIFEST.MF") == null) {
    throw new IllegalArgumentException(modFile + " is not a Forge 1.13+ mod; try ForgeOldModMetadata");
}
LocalModFile mod = ForgeNewModMetadata.fromFile(modManager, modFile, tree, ModLoaderType.FORGE);
Defensive patterns

Strategy: try-catch

Validate before calling

boolean looksLikeModernForge(ZipFileTree tree) {
    return tree.getEntry("META-INF/mods.toml") != null
        || tree.getEntry("META-INF/neoforge.mods.toml") != null
        || tree.getEntry("META-INF/MANIFEST.MF") != null;
}

Try / catch

try {
    LocalModFile mod = ForgeNewModMetadata.fromFile(modManager, modFile, tree, loaderType);
} catch (IOException | JsonParseException e) {
    // fall back to legacy parser or skip the jar
}

Prevention

When it happens

Trigger: Calling ForgeNewModMetadata.fromFile (directly or via fromForgeFile/fromNeoForgeFile/fromEmbeddedMod) on a jar where the toml entry is absent AND the embedded-mod path failed (e.g. no MANIFEST.MF, no jarjar metadata, or embedded entries unreadable).

Common situations: Scanning a legacy Forge 1.12 mod (uses mcmod.info) through the modern Forge parser; a broken or truncated jar; a Fabric or plain library jar misidentified as Forge.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10). Data as JSON: /api/errors/7729f68c4fcdc556. Report an issue: GitHub.

Appendix: source

Thrown at HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/ForgeNewModMetadata.java:210

        if (modLoaderType == ModLoaderType.NEO_FORGE) {
            try {
                return fromFile0("META-INF/neoforge.mods.toml", modLoaderType, modManager, modFile, tree);
            } catch (Exception ignored) {
            }
        }

        try {
            return fromFile0("META-INF/mods.toml", modLoaderType, modManager, modFile, tree);
        } catch (Exception ignored) {
        }

        try {
            return fromEmbeddedMod(modManager, modFile, tree, modLoaderType);
        } catch (Exception ignored) {
        }

        throw new IOException("File " + modFile + " is not a Forge 1.13+ or NeoForge mod.");
    }

    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);

View on GitHub (pinned to 24702dc5a0)