HMCL-dev/HMCL · error · IOException
Missing release file
Error message
Missing release file
What it means
JavaManagementPage.onAddJavaHome registers an external JDK directory with HMCL. A valid JDK/JRE installation must contain a `release` metadata file at its root; if file.resolve("release") does not exist, the page throws IOException("Missing release file " + releaseFile) and shows the i18n "java.add.failed" dialog.
Solutions
- Select the JDK root directory (the one containing both `bin/` and `release`), not bin/ or its parent
- Verify the `release` file exists: ls <chosen-dir>/release
- Re-extract the JDK archive if it was unpacked incompletely or files were stripped
- Use a full JDK distribution rather than a minimal/custom runtime image
Example fix
// before
Path releaseFile = file.resolve("release");
if (Files.notExists(releaseFile))
throw new IOException("Missing release file " + releaseFile);
// after
// caller-side guard before invoking onAddJavaHome
if (file.getFileName().toString().equals("bin"))
file = file.getParent(); // user picked <jdk>/bin, walk up to <jdk> Defensive patterns
Strategy: validation
Validate before calling
// before calling onAddJavaHome
if (file.getFileName().toString().equals("bin"))
file = file.getParent();
if (!Files.isRegularFile(file.resolve("release"))) {
Controllers.dialog("Selected directory has no 'release' file — pick the JDK root folder");
return;
} Try / catch
try {
addJavaHome(path);
} catch (IOException e) {
if (e.getMessage().startsWith("Missing release file")) {
Controllers.dialog("Pick the JDK root directory (contains bin/ and release), not bin/ or its parent");
} else throw e;
} Prevention
- Always select the directory that contains both `bin/` and `release`
- Verify the `release` file exists before adding a Java home
- Avoid jlink-minimal or manually stripped runtime images
When it happens
Trigger: Using "Add Java home" and pointing at a directory that lacks a `release` file at its top level — e.g. the bin/ directory, a folder one level too high, or a stripped JRE image.
Common situations: Selecting <jdk>/bin instead of <jdk>; selecting the parent of the JDK; jlink/jpackage-produced minimal images that drop the release file; manually copied JDKs missing metadata files.
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.
Related errors
- authlib-injectors.json -> urls cannot be null.
- Missing protected payload member: protection
- code is null
- profiles is null
- Host cannot be blank
AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10).
Data as JSON: /api/errors/1970e34967275bc9.
Report an issue: GitHub.
Appendix: source
Thrown at HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaManagementPage.java:149
return;
}
Controllers.navigateForward(new JavaRestorePage(SettingsManager.userSettings().getDisabledJava()));
}
private void onAddJavaBinary(Path file) {
JavaManager.getAddJavaTask(file).whenComplete(Schedulers.javafx(), exception -> {
if (exception != null) {
LOG.warning("Failed to add java", exception);
Controllers.dialog(i18n("java.add.failed"), i18n("message.error"), MessageDialogPane.MessageType.ERROR);
}
}).start();
}
private void onAddJavaHome(Path file) {
Task.composeAsync(() -> {
Path releaseFile = file.resolve("release");
if (Files.notExists(releaseFile))
throw new IOException("Missing release file " + releaseFile);
return JavaManager.getAddJavaTask(file.resolve("bin").resolve(OperatingSystem.CURRENT_OS.getJavaExecutable()));
}).whenComplete(Schedulers.javafx(), exception -> {
if (exception != null) {
LOG.warning("Failed to add java", exception);
Controllers.dialog(i18n("java.add.failed"), i18n("message.error"), MessageDialogPane.MessageType.ERROR);
}
}).start();
}
private void onInstallArchive(Path file) {
Task.supplyAsync(() -> {
try (ArchiveFileTree<?, ?> tree = ArchiveFileTree.open(file)) {
JavaInfo info = JavaInfo.fromArchive(tree);
if (!JavaManager.isCompatible(info.getPlatform()))
throw new UnsupportedPlatformException(info.getPlatform().toString());
return Pair.pair(tree.getRoot().getSubDirs().keySet().iterator().next(), info);View on GitHub (pinned to 24702dc5a0)