HMCL-dev/HMCL · error · JsonParseException

"Unexpected first token: " + firstToken

Error message

"Unexpected first token: " + firstToken

What it means

ForgeOldModMetadata.fromFile expects `mcmod.info` to start with either a JSON array (a mod list) or an object (`{"modList": ...}`). Any other leading token (string, number, boolean, END) triggers a JsonParseException naming the unexpected token.

Solutions

  1. Inspect `mcmod.info` in the jar and make the root a JSON array or an object with `modList`
  2. Re-obtain the mod jar from its official release
  3. Exclude the jar from scanning if it is not a Forge mod

Example fix

// before (invalid root)
"not a mod list"
// after
[{ "modid": "example", "name": "Example", "version": "1.0" }]
Defensive patterns

Strategy: validation

Validate before calling

String s = readMcmodInfo(tree);
String t = s.trim();
if (!(t.startsWith("[") || t.startsWith("{")))
    throw new IllegalStateException("mcmod.info must start with '[' or '{'");

Try / catch

try {
    ForgeOldModMetadata.fromFile(modManager, modFile, tree);
} catch (JsonParseException e) {
    log.warn("Unexpected mcmod.info structure: " + e.getMessage());
}

Prevention

When it happens

Trigger: Calling ForgeOldModMetadata.fromFile on a jar whose `mcmod.info` begins with a scalar like `"foo"`, `42`, or `true` instead of an array or object.

Common situations: Placeholder `mcmod.info` files written by hand; build scripts accidentally embedding a plain string; truncation leaving a partial scalar.

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.

Related errors


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

Appendix: source

Thrown at HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/ForgeOldModMetadata.java:146

        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(),
                StringUtils.isBlank(metadata.getUrl()) ? metadata.getUpdateUrl() : metadata.url,
                metadata.getLogoFile());
    }

View on GitHub (pinned to 24702dc5a0)