HMCL-dev/HMCL · error · IOException
Malformed datapack zip
Error message
Malformed datapack zip
What it means
DataPack.installPack inspects a datapack archive to determine its layout: a 'datapacks' subdirectory means the zip contains multiple packs, a pack.mcmeta at the root means a single pack. If neither exists, the archive does not match any known datapack structure and this IOException is thrown.
Solutions
- Verify the zip contains pack.mcmeta at its root (or a datapacks/ folder) before importing
- Re-download the datapack from its source — the archive may be corrupted
- Unwrap nested folders: if the zip contains a folder/pack.mcmeta, rezip so pack.mcmeta is at the root
- Check you selected a datapack, not a resource pack or mod file
Example fix
// before: mypack.zip -> mypack/pack.mcmeta (nested) // after: rezip so structure is mypack.zip -> pack.mcmeta zip -r mypack.zip pack.mcmeta assets data
Defensive patterns
Strategy: validation
Validate before calling
try (java.util.zip.ZipFile zip = new java.util.zip.ZipFile(packFile.toFile())) {
boolean hasMcmeta = zip.getEntry("pack.mcmeta") != null;
boolean hasDatapacks = zip.getEntry("datapacks/") != null;
if (!hasMcmeta && !hasDatapacks) {
throw new IllegalArgumentException(packFile + " is not a valid datapack zip");
}
} Try / catch
try {
dataPack.installPack(target);
} catch (IOException e) {
if (e.getMessage().contains("Malformed datapack")) {
LOG.warning("Selected file is not a valid datapack (missing pack.mcmeta / datapacks dir)");
// prompt user to pick a correct file
}
} Prevention
- Check for pack.mcmeta at the zip root before importing
- Distinguish datapacks from resource packs and mods before selection
- Rezip nested packs so pack.mcmeta sits at the archive root
- Validate downloaded archives for corruption (size/checksum) before install
When it happens
Trigger: Calling installPack on a zip file whose root contains neither a datapacks/ directory nor a pack.mcmeta — i.e. the file is not a valid Minecraft datapack.
Common situations: User selects the wrong file (a resource pack, mod, or random zip) in the datapack importer; a corrupted/partially downloaded datapack zip; a pack nested one level too deep so mcmeta sits in a subfolder.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Not a valid world zip file
- Not a valid world zip file since level.dat or…
- World zip malformed
- Cannot recognize the game version of modpack
- Duplicate theme-pack zip entry:
AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10).
Data as JSON: /api/errors/7692d1e8b33f0e18.
Report an issue: GitHub.
Appendix: source
Thrown at HMCLCore/src/main/java/org/jackhuang/hmcl/addon/datapack/DataPack.java:74
}
public ObservableList<Pack> getPacks() {
return packs;
}
public static void installPack(Path sourceDataPackPath, Path targetDataPackDirectory, GameVersionNumber gameVersionNumber) throws IOException {
boolean containsMultiplePacks;
Set<String> packs = new HashSet<>();
try (FileSystem fs = CompressingUtils.readonly(sourceDataPackPath).setAutoDetectEncoding(true).build()) {
Path dataPacks = fs.getPath("datapacks");
Path mcmeta = fs.getPath("pack.mcmeta");
if (Files.exists(dataPacks)) {
containsMultiplePacks = true;
} else if (Files.exists(mcmeta)) {
containsMultiplePacks = false;
} else {
throw new IOException("Malformed datapack zip");
}
if (containsMultiplePacks) {
try (Stream<Path> s = Files.list(dataPacks)) {
packs = s.map(FileUtils::getNameWithoutExtension).collect(Collectors.toSet());
}
} else {
packs.add(FileUtils.getNameWithoutExtension(sourceDataPackPath));
}
try (DirectoryStream<Path> stream = Files.newDirectoryStream(targetDataPackDirectory)) {
for (Path dir : stream) {
String packName = FileUtils.getName(dir);
if (FileUtils.getExtension(dir).equals(DISABLED_EXT)) {
packName = StringUtils.removeSuffix(packName, "." + DISABLED_EXT);
}
packName = FileUtils.getNameWithoutExtension(packName);
if (packs.contains(packName)) {View on GitHub (pinned to 24702dc5a0)