HMCL-dev/HMCL · error · IOException

File is not a Fabric mod.

Error message

File ${modFile} is not a Fabric mod.

What it means

Thrown by FabricModMetadata.fromFile when a jar passed to the mod manager has no fabric.mod.json entry. HMCL identifies Fabric mods by parsing fabric.mod.json at the jar root; without it the file cannot be recognized as a Fabric mod and metadata cannot be extracted. This is a guard against calling the parser on an unsupported or corrupt jar.

Solutions

  1. Verify the jar actually contains fabric.mod.json at its root (e.g. unzip -l mod.jar | grep fabric.mod.json); if not, it is not a Fabric mod.
  2. Re-download the mod from its official source; the jar is likely corrupt, wrong-loader, or repackaged incorrectly.
  3. If it is a Forge/NeoForge mod, use ForgeOldModMetadata/ForgeNewModMetadata.fromFile instead of the Fabric parser.
  4. If repackaging, ensure fabric.mod.json sits at the archive root, not in a nested folder.

Example fix

// before
LocalModFile mod = FabricModMetadata.fromFile(modManager, modFile, tree);
// after
if (tree.getEntry("fabric.mod.json") == null) {
    throw new IllegalArgumentException(modFile + " has no fabric.mod.json; not a Fabric mod");
}
LocalModFile mod = FabricModMetadata.fromFile(modManager, modFile, tree);
Defensive patterns

Strategy: validation

Validate before calling

if (tree.getEntry("fabric.mod.json") == null) {
    // not a Fabric mod; route to another loader parser or skip
    return null;
}

Type guard

boolean isFabricMod(ZipFileTree tree) { return tree.getEntry("fabric.mod.json") != null; }

Try / catch

try {
    LocalModFile mod = FabricModMetadata.fromFile(modManager, modFile, tree);
} catch (IOException e) {
    LOG.warning("Skipping non-Fabric jar: " + modFile);
}

Prevention

When it happens

Trigger: Calling FabricModMetadata.fromFile(modManager, modFile, tree) on a jar whose ZipFileTree has no entry named 'fabric.mod.json'.

Common situations: Pointing the mod scanner at a Forge/Quilt-lite or plain library jar; a manually repackaged mod where fabric.mod.json was moved to a subdirectory or deleted; scanning non-mod jars in the mods folder.

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/23fe7559516be5ed. Report an issue: GitHub.

Appendix: source

Thrown at HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/FabricModMetadata.java:66

    public FabricModMetadata() {
        this("", "", "", "", "", Collections.emptyList(), Collections.emptyMap());
    }

    public FabricModMetadata(String id, String name, String version, String icon, String description, List<FabricModAuthor> authors, Map<String, String> contact) {
        this.id = id;
        this.name = name;
        this.version = version;
        this.icon = icon;
        this.description = description;
        this.authors = authors;
        this.contact = contact;
    }

    public static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFileTree tree) throws IOException, JsonParseException {
        ZipArchiveEntry mcmod = tree.getEntry("fabric.mod.json");
        if (mcmod == null)
            throw new IOException("File " + modFile + " is not a Fabric mod.");
        FabricModMetadata metadata = JsonUtils.fromNonNullJsonFully(tree.getInputStream(mcmod), FabricModMetadata.class);
        String authors = metadata.authors == null ? "" : metadata.authors.stream().map(author -> author.name).collect(Collectors.joining(", "));
        return new LocalModFile(modManager, modManager.getLocalMod(metadata.id, ModLoaderType.FABRIC), modFile, metadata.name, new LocalAddonFile.Description(metadata.description),
                authors, metadata.version, "", metadata.contact != null ? metadata.contact.getOrDefault("homepage", "") : "", metadata.icon);
    }

    @JsonAdapter(FabricModAuthorSerializer.class)
    public static final class FabricModAuthor {
        private final String name;

        public FabricModAuthor() {
            this("");
        }

        public FabricModAuthor(String name) {
            this.name = name;
        }
    }

View on GitHub (pinned to 24702dc5a0)