HMCL-dev/HMCL · error · IOException

Not a valid world zip file since level.dat or…

Error message

Not a valid world zip file since level.dat or special_level.dat cannot be found.

What it means

Thrown by World(Path) when a zip-based world archive does not contain level.dat at its root (or a nested single root directory), nor the special_level.dat variant used by the 20w14infinite April Fools snapshot. HMCL requires one of these NBT files to parse world metadata, so an archive without them cannot be a Minecraft world.

Solutions

  1. Verify the zip actually contains level.dat (or special_level.dat) at its root or exactly one top-level folder that contains it
  2. Re-export the world from Minecraft or HMCL so the archive includes level.dat
  3. If importing a 20w14infinite world, confirm special_level.dat is present inside the zip
  4. Check the zip is not truncated or corrupted (test unzip) and retry

Example fix

// before
World world = new World(Path.of("download.zip")); // zip has no level.dat
// after
try (FileSystem fs = CompressingUtils.readonly(Path.of("download.zip")).build()) {
    if (!Files.exists(fs.getPath("/level.dat"))) throw new IllegalArgumentException("zip lacks level.dat");
}
World world = new World(Path.of("download.zip"));
Defensive patterns

Strategy: validation

Validate before calling

static boolean looksLikeWorldZip(Path zip) throws IOException {
    try (FileSystem fs = CompressingUtils.readonly(zip).setAutoDetectEncoding(true).build()) {
        if (Files.isRegularFile(fs.getPath("/level.dat")) || Files.isRegularFile(fs.getPath("/special_level.dat"))) return true;
        try (Stream<Path> s = Files.list(fs.getPath("/"))) {
            List<Path> top = s.toList();
            return top.size() == 1 && Files.isDirectory(top.get(0))
                && (Files.isRegularFile(top.get(0).resolve("level.dat")) || Files.isRegularFile(top.get(0).resolve("special_level.dat")));
        }
    }
}

Type guard

static boolean isWorldZip(Path p) {
    return Files.isRegularFile(p) && p.toString().endsWith(".zip");
}

Try / catch

try {
    World world = new World(zipPath);
} catch (IOException e) {
    if (String.valueOf(e.getMessage()).contains("Not a valid world zip")) {
        // surface 'archive has no level.dat' to the user and abort import
    }
}

Prevention

When it happens

Trigger: Constructing new World(zipPath) where file.isDirectory() is false and the zip archive contains no root-level level.dat and no special_level.dat after resolving a single top-level subdirectory.

Common situations: Exporting a world incorrectly (zip created without level.dat), archiving only part of the saves folder, passing a random zip file or an empty/corrupted zip, or a zip with multiple nested directories where level.dat is deeper than one level.

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


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

Appendix: source

Thrown at HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java:107

                    fileName = FileUtils.getName(this.file);
                } else {
                    try (Stream<Path> filesStream = Files.list(fs.getPath("/"))) {
                        List<Path> files = filesStream.toList();
                        if (files.size() != 1 || !Files.isDirectory(files.get(0))) {
                            throw new IOException("Not a valid world zip file");
                        }

                        root = files.get(0);
                        fileName = FileUtils.getName(root);
                    }
                }

                Path levelDat = root.resolve("level.dat");
                if (!Files.exists(levelDat)) { //version 20w14infinite
                    levelDat = root.resolve("special_level.dat");
                }
                if (!Files.exists(levelDat)) {
                    throw new IOException("Not a valid world zip file since level.dat or special_level.dat cannot be found.");
                }
                loadAndCheckLevelData(levelDat);

                Path iconFile = root.resolve("icon.png");
                if (Files.isRegularFile(iconFile)) {
                    try (InputStream inputStream = Files.newInputStream(iconFile)) {
                        icon = new Image(inputStream, 64, 64, true, false);
                        if (icon.isError())
                            throw icon.getException();
                    } catch (Exception e) {
                        LOG.warning("Failed to load world icon", e);
                    }
                }
            }
        else
            throw new IOException("Path " + file + " cannot be recognized as a Minecraft world");
    }

View on GitHub (pinned to 24702dc5a0)