HMCL-dev/HMCL · error · IOException

Not a valid world zip file

Error message

Not a valid world zip file

What it means

When a world file is a zip, the constructor expects either level.dat/special_level.dat at the zip root or exactly one top-level directory containing them. Otherwise it cannot locate the world root and throws IOException 'Not a valid world zip file'.

Solutions

  1. Re-zip so the archive contains exactly one top-level directory holding level.dat
  2. Extract the zip and import via the directory path instead
  3. Remove stray root-level files (e.g. __MACOSX, README) and keep a single world folder
  4. Download the world zip again from the original source

Example fix

// before
world.zip
├── level.dat
├── region/
└── screenshot.png   // extra root entries -> rejected
// after
world.zip
└── MyWorld/
    ├── level.dat
    └── region/
Defensive patterns

Strategy: validation

Validate before calling

try (FileSystem fs = FileSystems.newFileSystem(zipPath)) {
    try (Stream<Path> s = Files.list(fs.getPath("/"))) {
        List<Path> top = s.toList();
        boolean ok = top.size() == 1 && Files.isDirectory(top.get(0))
            && Files.exists(top.get(0).resolve("level.dat"));
        if (!ok) throw new IllegalArgumentException("Zip must contain exactly one world folder with level.dat");
    }
}

Type guard

static boolean isValidWorldZip(Path zip) {
    try (FileSystem fs = FileSystems.newFileSystem(zip)) {
        try (Stream<Path> s = Files.list(fs.getPath("/"))) {
            List<Path> top = s.toList();
            return top.size() == 1 && Files.isDirectory(top.get(0))
                && Files.exists(top.get(0).resolve("level.dat"));
        }
    } catch (IOException e) { return false; }
}

Try / catch

try {
    World w = new World(zipFile);
} catch (IOException e) {
    if (e.getMessage().contains("Not a valid world zip file")) {
        // extract manually or ask user to re-zip with a single world folder
    } else throw e;
}

Prevention

When it happens

Trigger: Opening a zip whose root holds level.dat plus sibling files (screenshot zips, multi-world archives, zips made on macOS with __MACOSX folders), or a zip with multiple top-level directories.

Common situations: Users zipping multiple worlds together, browser downloading a page-wrapped zip, macOS Finder zips adding extra root entries, modpack exports with README at root.

Related errors


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

Appendix: source

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

                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 if (Files.isRegularFile(file))
            try (FileSystem fs = CompressingUtils.readonly(this.file).setAutoDetectEncoding(true).build()) {
                Path root;
                if (Files.isRegularFile(fs.getPath("/level.dat"))) {
                    root = fs.getPath("/");
                    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)) {

View on GitHub (pinned to 24702dc5a0)