HMCL-dev/HMCL · error · IOException
World zip malformed
Error message
World zip malformed
What it means
World.install throws IOException("World zip malformed") when the zip has no root-level level.dat and its top level does not contain exactly one subdirectory. HMCL needs an unambiguous world root (either level.dat at '/' or a single top-level folder) to know what to extract.
Solutions
- Repackage the zip so level.dat is at the root, or so exactly one top-level directory contains it
- Remove extraneous root entries (readme files, .DS_Store, extra folders) from the archive
- Flatten double-nested archives (zip/world/level.dat -> zip/level.dat)
- If several worlds are bundled, split them into one zip per world
Example fix
// before: zip layout: [WorldA/, WorldB/, readme.txt]
new World(Path.of("bundle.zip")).install(savesDir, "x"); // malformed
// after: one zip per world with level.dat at root
// bundle.zip layout: [level.dat, region/, ...]
new World(Path.of("worldA.zip")).install(savesDir, "WorldA"); Defensive patterns
Strategy: validation
Validate before calling
static boolean installableZipLayout(Path zip) throws IOException {
try (FileSystem fs = CompressingUtils.readonly(zip).setAutoDetectEncoding(true).build()) {
if (Files.isRegularFile(fs.getPath("/level.dat"))) return true;
try (Stream<Path> s = Files.list(fs.getPath("/"))) {
List<Path> top = s.filter(p -> !p.getFileName().toString().startsWith(".")).toList();
return top.size() == 1 && Files.isDirectory(top.get(0));
}
}
} Try / catch
try {
world.install(savesDir, name);
} catch (IOException e) {
if (String.valueOf(e.getMessage()).equals("World zip malformed")) {
// repackage the zip with level.dat at root or a single top-level folder
}
} Prevention
- Create world zips by zipping the world folder's contents, or exactly the folder itself
- Exclude stray files (.DS_Store, Thumbs.db, readme.txt) from archive roots
- Split multi-world bundles into one archive per world
- Avoid double-nesting (zip/world/world/level.dat)
When it happens
Trigger: Calling world.install on a zip that lacks /level.dat and whose '/' listing yields 0 or 2+ entries (multiple top-level folders, or loose files plus folders, or an empty zip).
Common situations: Zips created by selecting several world folders at once, archives with a README or .DS_Store sitting next to the world folder in the root, archives where the world is nested two levels deep (world/level.dat under an extra folder), or empty zips.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Malformed datapack zip
- Not a valid world zip file
- Not a valid world zip file since level.dat or…
- World already exists
- Cannot recognize the game version of modpack
AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10).
Data as JSON: /api/errors/35f1779e3cd40ff0.
Report an issue: GitHub.
Appendix: source
Thrown at HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java:341
throw new IOException(e);
}
if (Files.isDirectory(worldDir)) {
throw new FileAlreadyExistsException("World already exists");
}
if (Files.isRegularFile(file)) {
try (FileSystem fs = CompressingUtils.readonly(file).setAutoDetectEncoding(true).build()) {
Path levelDatPath = fs.getPath("/level.dat");
if (Files.isRegularFile(levelDatPath)) {
fileName = FileUtils.getName(file);
new Unzipper(file, worldDir).unzip();
} else {
try (Stream<Path> stream = Files.list(fs.getPath("/"))) {
List<Path> subDirs = stream.toList();
if (subDirs.size() != 1) {
throw new IOException("World zip malformed");
}
String subDirectoryName = FileUtils.getName(subDirs.get(0));
new Unzipper(file, worldDir)
.setSubDirectory("/" + subDirectoryName + "/")
.unzip();
}
}
}
new World(worldDir).rename(name);
} else if (Files.isDirectory(file)) {
FileUtils.copyDirectory(file, worldDir);
}
}
public void export(Path zip, String worldName) throws IOException {
if (!Files.isDirectory(file))
throw new IOException();View on GitHub (pinned to 24702dc5a0)