HMCL-dev/HMCL · error · ArtifactMalformedException
Malformed forge installer file, install_profile.json does…
Error message
Malformed forge installer file, install_profile.json does not exist.
What it means
ForgeOldInstallTask.execute() opens the legacy Forge installer jar as a ZipFile and requires an install_profile.json entry. If getEntry("install_profile.json") returns null (no stream), ArtifactMalformedException("Malformed forge installer file, install_profile.json does not exist.") is thrown — the file is not a legacy Forge universal/installer jar at all.
Solutions
- Re-download the correct '-installer.jar' artifact (not the universal jar) from files.minecraftforge.net
- Verify install_profile.json exists inside the jar: 'unzip -l forge-installer.jar | grep install_profile'
- Update HMCL — newer builds route new-format installers to ForgeNewInstallTask
- Clear the download cache so a stale/wrong cached installer is not reused
Defensive patterns
Strategy: validation
Validate before calling
// confirm this is a legacy installer containing install_profile.json
try (var zip = new java.util.zip.ZipFile(installer.toFile())) {
if (zip.getEntry("install_profile.json") == null)
throw new IllegalStateException("not a legacy forge installer");
} Try / catch
try { installTask.execute(); } catch (ArtifactMalformedException e) { if (e.getMessage().contains("install_profile.json")) { downloadCorrectInstallerArtifact(); } else throw e; } Prevention
- Download the '-installer.jar' artifact, never the '-universal.jar', for legacy Forge
- Ensure HMCL routes new-format installers (with install_title.json) to the new task
- List zip entries before use when handling forge jars programmatically
- Clear stale cached installers
When it happens
Trigger: installer path passed to ForgeOldInstallTask points to a zip/jar lacking install_profile.json — e.g. a Forge universal jar, a 'installer-win' variant with different layout, or a new-format installer handled by the wrong task; also plain corrupt files that still open as zip.
Common situations: Downloaded the wrong artifact (universal instead of installer) from the Forge file server; mirror served an error page that happens to be a zip; HMCL routed a new-format installer (install_title metadata) to the old installer path; user manually swapped the cached file.
Understand the failure class
Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Malformed forge installer file
- Malformed forge installer file
- Illegal pattern (Bad escape):
- Illegal pattern (Unclosed ):
- Illegal pattern: Missing Key:
AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10).
Data as JSON: /api/errors/0ad2dddcc36b75d8.
Report an issue: GitHub.
Appendix: source
Thrown at HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeOldInstallTask.java:73
setSignificance(TaskSignificance.MAJOR);
}
@Override
public List<Task<?>> getDependencies() {
return dependencies;
}
@Override
public boolean doPreExecute() {
return true;
}
@Override
public void execute() throws Exception {
try (ZipFile zipFile = new ZipFile(installer.toFile())) {
InputStream stream = zipFile.getInputStream(zipFile.getEntry("install_profile.json"));
if (stream == null)
throw new ArtifactMalformedException("Malformed forge installer file, install_profile.json does not exist.");
ForgeInstallProfile installProfile = JsonUtils.fromNonNullJsonFully(stream, ForgeInstallProfile.class);
// unpack the universal jar in the installer file.
Library forgeLibrary = new Library(installProfile.install().getPath());
GameRepository gameRepository = dependencyManager.getGameRepository();
Path forgeFile = gameRepository.getLayout().getLibraryFile(manifest.id(), forgeLibrary);
Files.createDirectories(forgeFile.getParent());
ZipEntry forgeEntry = zipFile.getEntry(installProfile.install().getFilePath());
try (InputStream is = zipFile.getInputStream(forgeEntry);
OutputStream os = Files.newOutputStream(forgeFile, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
is.transferTo(os);
}
setResult(GameInstancePatch.fromManifest(
installProfile.versionInfo(),
GameComponentType.FORGE.getPatchId(),
selfVersion,View on GitHub (pinned to 24702dc5a0)