HMCL-dev/HMCL · error · IOException
Theme-pack entry is empty
Error message
Theme-pack entry is empty
What it means
normalizeThemePackEntryName trims whitespace, converts backslashes to slashes, and strips one trailing slash from each zip entry name. If nothing remains after that normalization, the entry name is empty and this IOException is thrown. Empty entry names cannot map to a filesystem path.
Solutions
- Remove the empty-named entry from the zip (repack excluding it).
- Regenerate the archive with a reliable zip tool or ThemePackExporter.
- If generating entries programmatically, assert names are non-empty before writing them.
Example fix
// before
zip.putNextEntry(new ZipArchiveEntry(" "));
// after
if (!name.isBlank()) zip.putNextEntry(new ZipArchiveEntry(name.trim().replace('\\', '/'))); Defensive patterns
Strategy: validation
Validate before calling
for (var e : Collections.list(new ZipFile(pack).entries())) {
if (e.getName().isBlank() || e.getName().equals("/")) throw new IllegalArgumentException("empty entry name in " + pack);
} Try / catch
try {
ThemePackManager.install(pack, dir);
} catch (IOException e) {
if (e.getMessage().contains("entry is empty")) {
ui.show("Archive is corrupted (blank entry name); re-download or re-export it.");
} else throw e;
} Prevention
- Skip null/blank-named entries when creating zips programmatically.
- Regenerate suspicious archives rather than patching them.
- Use well-maintained zip tooling.
When it happens
Trigger: Installing a theme-pack zip containing an entry whose name is empty, whitespace-only, or just '/'; archives created by buggy tools writing blank-name entries.
Common situations: Corrupted or programmatically generated zips with blank entry names; entries consisting only of '/' created by faulty packers.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- Theme-pack entry name is not normalized
- Theme-pack asset entry is empty
- Theme-pack asset entry must be relative:
- Theme-pack asset entry must be under assets/:
- Theme-pack asset entry must be a file:
AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10).
Data as JSON: /api/errors/d751dcd2e737544f.
Report an issue: GitHub.
Appendix: source
Thrown at HMCL/src/main/java/org/jackhuang/hmcl/theme/ThemePackManager.java:1403
/// Moves a file into place, using an atomic move when the platform supports it.
private static void moveReplacing(Path source, Path target) throws IOException {
try {
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
} catch (AtomicMoveNotSupportedException e) {
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);
}
}
/// Returns a normalized and safe theme-pack zip entry name.
private static String normalizeThemePackEntryName(String entryName) throws IOException {
Objects.requireNonNull(entryName);
String normalized = entryName.trim().replace('\\', '/');
if (normalized.endsWith("/")) {
normalized = normalized.substring(0, normalized.length() - 1);
}
if (normalized.isEmpty()) {
throw new IOException("Theme-pack entry is empty");
}
if (normalized.startsWith("/") || normalized.matches("^[A-Za-z]:.*")) {
throw new IOException("Theme-pack entry must be relative: " + entryName);
}
for (String segment : normalized.split("/")) {
if (segment.isEmpty() || ".".equals(segment) || "..".equals(segment)) {
throw new IOException("Theme-pack entry contains an unsafe segment: " + entryName);
}
for (int i = 0; i < segment.length(); i++) {
char ch = segment.charAt(i);
if (Character.isISOControl(ch) || ch == '\0') {
throw new IOException("Theme-pack entry contains a control character: " + entryName);
}
}
}
return normalized;
}View on GitHub (pinned to 24702dc5a0)