ZIP - convenience methods
July 1, 2026 · View on GitHub
Quick Overview
The project was started and coded by Rein Raudjärv when he needed to process a large set of large ZIP archives for LiveRebel internals. Soon after we started using the utility in other projects because of the ease of use and it just worked.
The project is built using java.util.zip.* packages for stream based access. Most convenience methods for filesystem usage is also supported.
Installation
The project artifacts are available in Maven Central Repository.
Maven:
<dependency>
<groupId>org.zeroturnaround</groupId>
<artifactId>zt-zip</artifactId>
<version>1.18.1</version>
<type>jar</type>
</dependency>
Gradle:
implementation("org.zeroturnaround:zt-zip:1.18.1")
Notice that 1.8 is the last Java 1.4 compatible release. Releases from 1.9 onwards require Java 5 at the source level, and current releases require Java 8 or later at runtime.
If you are using with ProGuard, please add the following configuration
-dontwarn org.slf4j.**
Background
We had the following functional requirements:
- pack and unpack directories recursively
- include/exclude entries
- rename entries
- packing/unpacking in place - ZIP becomes directory and vice versa
- iterate through ZIP entries
- add or replace entries from files or byte arrays
- transform ZIP entries
- compare two archives - compare all entries ignoring time stamps
and these non-functional requirements:
- use existing APIs as much as possible
- be simple to use
- be effective to use - do not traverse an entire ZIP file if only a single entry is needed
- be safe to use - do not enable user to leave streams open and keep files locked
- do not declare exceptions
- be compatible with Java 1.5
Security considerations
When unpacking, zt-zip rejects entries whose path would resolve outside the target directory (the "Zip Slip" / directory-traversal class of attack), throwing MaliciousZipException. An entry whose path resolves to the target directory itself (for example an entry named /) is a no-op: its stored file permissions are not applied to the target directory.
zt-zip does not impose any limit on the decompressed size, compression ratio, or number of entries of an archive. Unpacking an untrusted archive can therefore exhaust memory or disk through a zip bomb: unpack streams entries to disk but never caps the total bytes written or the entry count, and unpackEntry loads a whole entry into memory at once. If you extract archives from untrusted sources, validate the source and enforce your own limits — for example cap the number of entries, the size of each entry, and the total uncompressed size, and prefer streaming over unpackEntry for large or untrusted entries.
Examples
The examples don't include all the possible ways how to use the library but will give an overview. When you see a method that is useful but doesn't necessarily solve your use case then just head over to ZipUtil.class file and see the sibling methods that are named the same but arguments might be different.
Unpacking
Check if an entry exists in a ZIP archive
boolean exists = ZipUtil.containsEntry(new File("/tmp/demo.zip"), "foo.txt");
Extract an entry from a ZIP archive into a byte array
byte[] bytes = ZipUtil.unpackEntry(new File("/tmp/demo.zip"), "foo.txt");
Extract an entry from a ZIP archive with a specific Charset into a byte array
byte[] bytes = ZipUtil.unpackEntry(new File("/tmp/demo.zip"), "foo.txt", Charset.forName("IBM437"));
Extract an entry from a ZIP archive into file system
ZipUtil.unpackEntry(new File("/tmp/demo.zip"), "foo.txt", new File("/tmp/bar.txt"));
Extract a ZIP archive
ZipUtil.unpack(new File("/tmp/demo.zip"), new File("/tmp/demo"));
Extract a ZIP archive which becomes a directory
ZipUtil.explode(new File("/tmp/demo.zip"));
Extract a directory from a ZIP archive including the directory name
ZipUtil.unpack(new File("/tmp/demo.zip"), new File("/tmp/demo"), new NameMapper() {
public String map(String name) {
return name.startsWith("doc/") ? name : null;
}
});
Extract a directory from a ZIP archive excluding the directory name
final String prefix = "doc/";
ZipUtil.unpack(new File("/tmp/demo.zip"), new File("/tmp/demo"), new NameMapper() {
public String map(String name) {
return name.startsWith(prefix) ? name.substring(prefix.length()) : name;
}
});
Extract files from a ZIP archive that match a name pattern
ZipUtil.unpack(new File("/tmp/demo.zip"), new File("/tmp/demo"), new NameMapper() {
public String map(String name) {
if (name.contains("/doc")) {
return name;
}
else {
// returning null from the map method will disregard the entry
return null;
}
}
});
Print .class entry names in a ZIP archive
ZipUtil.iterate(new File("/tmp/demo.zip"), new ZipInfoCallback() {
public void process(ZipEntry zipEntry) throws IOException {
if (zipEntry.getName().endsWith(".class"))
System.out.println("Found " + zipEntry.getName());
}
});
Print .txt entries in a ZIP archive (uses IoUtils from Commons IO)
ZipUtil.iterate(new File("/tmp/demo.zip"), new ZipEntryCallback() {
public void process(InputStream in, ZipEntry zipEntry) throws IOException {
if (zipEntry.getName().endsWith(".txt")) {
System.out.println("Found " + zipEntry.getName());
IOUtils.copy(in, System.out);
}
}
});
Packing
Compress a directory into a ZIP archive
ZipUtil.pack(new File("/tmp/demo"), new File("/tmp/demo.zip"));
Compress a directory which becomes a ZIP archive
ZipUtil.unexplode(new File("/tmp/demo.zip"));
Compress a directory into a ZIP archive with a parent directory
ZipUtil.pack(new File("/tmp/demo"), new File("/tmp/demo.zip"), new NameMapper() {
public String map(String name) {
return "foo/" + name;
}
});
Compress a file into a ZIP archive
ZipUtil.packEntry(new File("/tmp/demo.txt"), new File("/tmp/demo.zip"));
Add an entry from file to a ZIP archive
ZipUtil.addEntry(new File("/tmp/demo.zip"), "doc/readme.txt", new File("f/tmp/oo.txt"), new File("/tmp/new.zip"));
Add an entry from byte array to a ZIP archive
ZipUtil.addEntry(new File("/tmp/demo.zip"), "doc/readme.txt", "bar".getBytes(), new File("/tmp/new.zip"));
Add an entry from file and from byte array to a ZIP archive
ZipEntrySource[] entries = new ZipEntrySource[] {
new FileSource("doc/readme.txt", new File("foo.txt")),
new ByteSource("sample.txt", "bar".getBytes())
};
ZipUtil.addEntries(new File("/tmp/demo.zip"), entries, new File("/tmp/new.zip"));
Add an entry from file and from byte array to a output stream
ZipEntrySource[] entries = new ZipEntrySource[] {
new FileSource("doc/readme.txt", new File("foo.txt")),
new ByteSource("sample.txt", "bar".getBytes())
};
OutputStream out = null;
try {
out = new BufferedOutputStream(new FileOutputStream(new File("/tmp/new.zip")));
ZipUtil.addEntries(new File("/tmp/demo.zip"), entries, out);
}
finally {
IOUtils.closeQuietly(out);
}
Replace a ZIP archive entry from file
boolean replaced = ZipUtil.replaceEntry(new File("/tmp/demo.zip"), "doc/readme.txt", new File("/tmp/foo.txt"), new File("/tmp/new.zip"));
Replace a ZIP archive entry from byte array
boolean replaced = ZipUtil.replaceEntry(new File("/tmp/demo.zip"), "doc/readme.txt", "bar".getBytes(), new File("/tmp/new.zip"));
Replace a ZIP archive entry from file and byte array
ZipEntrySource[] entries = new ZipEntrySource[] {
new FileSource("doc/readme.txt", new File("foo.txt")),
new ByteSource("sample.txt", "bar".getBytes())
};
boolean replaced = ZipUtil.replaceEntries(new File("/tmp/demo.zip"), entries, new File("/tmp/new.zip"));
Add or replace entries in a ZIP archive
ZipEntrySource[] addedEntries = new ZipEntrySource[] {
new FileSource("/path/in/zip/File1.txt", new File("/tmp/file1.txt")),
new FileSource("/path/in/zip/File2.txt", new File("/tmp/file2.txt")),
new FileSource("/path/in/zip/File3.txt", new File("/tmp/file2.txt")),
};
ZipUtil.addOrReplaceEntries(new File("/tmp/demo.zip"), addedEntries);
Transforming
Transform a ZIP archive entry into uppercase
boolean transformed = ZipUtil.transformEntry(new File("/tmp/demo"), "sample.txt", new StringZipEntryTransformer() {
protected String transform(ZipEntry zipEntry, String input) throws IOException {
return input.toUpperCase();
}
}, new File("/tmp/demo.zip"));
Comparison
Compare two ZIP archives (ignoring timestamps of the entries)
boolean equals = ZipUtil.archiveEquals(new File("/tmp/demo1.zip"), new File("/tmp/demo2.zip"));
Compare two ZIP archive entries with same name (ignoring timestamps of the entries)
boolean equals = ZipUtil.entryEquals(new File("/tmp/demo1.zip"), new File("/tmp/demo2.zip"), "foo.txt");
Compare two ZIP archive entries with different names (ignoring timestamps of the entries)
boolean equals = ZipUtil.entryEquals(new File("/tmp/demo1.zip"), new File("/tmp/demo2.zip"), "foo1.txt", "foo2.txt");
Building
The project is built with Gradle via the bundled wrapper, so no local Gradle installation is required:
./gradlew build
The library targets Java 8 bytecode. The build resolves a Java 8 toolchain automatically (it is downloaded on demand if not already installed), so the build itself runs on any modern JDK.
Releasing
Versions follow Semantic Versioning (MAJOR.MINOR.PATCH, e.g. 1.18.0)
and release tags use the vMAJOR.MINOR.PATCH form (e.g. v1.18.0). Tags created before this
convention use the older zt-zip-<version> form.
Releases are cut with the Release GitHub Actions workflow (Actions → Release → Run workflow).
It takes the release version (e.g. 1.18.0) and then builds, publishes to Maven Central, tags
the commit, creates the GitHub release, and sets the next development version.
The workflow also promotes the ## [Unreleased] section of CHANGELOG.md into a
dated ## [x.y.z] section and updates the comparison links, so the only changelog task is to make
sure the changes being released are listed under ## [Unreleased] beforehand. (The release fails
if that section is empty.)
Progress bar
There have been multiple requests for a progress bar. See ZT Zip Progress Bar for a sample implementation.
Debugging
The library is using the slf4j-api logging framework. All the log statements are either DEBUG or TRACE level. Depending on the logging framework you are using a simple -Dorg.slf4j.simpleLogger.defaultLogLevel=debug or System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "debug"); will do the trick of showing log statements from the zt-zip library. You can further fine tune the levels and inclusion of log messages per package with your logging framework.