1. Introduction: why you need archives and compression in Java
In today’s world, working with archives and compressed files is routine—backups, file exchange, logging, storing large datasets. Java provides standard tools for working with ZIP archives and GZIP-compressed files via the java.util.zip package.
What Java can do:
- Read and create ZIP archives (multi-file containers).
- Read and create GZIP files (single-file compression).
- Manage archive contents, filter files by mask.
- Control the compression level.
- Ensure safety during extraction (defend against zip slip and zip bomb).
2. Core classes
ZipInputStream and ZipOutputStream
These are stream classes for sequential reading/writing of ZIP archives. When to use: when you need to read or create an archive on the fly, without random access to individual files.
Example: reading an archive
import java.io.*;
import java.util.zip.*;
try (ZipInputStream zis = new ZipInputStream(new FileInputStream("archive.zip"))) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
System.out.println("File: " + entry.getName());
// You can read the entry contents via zis.read(...)
zis.closeEntry();
}
}
Example: creating an archive
import java.io.FileOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("archive.zip"))) {
ZipEntry entry = new ZipEntry("hello.txt");
zos.putNextEntry(entry);
zos.write("Hello, archive!".getBytes());
zos.closeEntry();
}
ZipFile
A class for random access to the contents of a ZIP archive: you can quickly get the list of files, open any file by name, and read its contents.
Example:
import java.util.zip.*;
import java.io.*;
ZipFile zipFile = new ZipFile("archive.zip");
zipFile.stream().forEach(entry -> System.out.println(entry.getName()));
ZipEntry entry = zipFile.getEntry("hello.txt");
try (InputStream is = zipFile.getInputStream(entry)) {
// Read file contents
}
zipFile.close();
When should you use ZipFile?
- If you need to quickly get the list of files, metadata, size, date.
- If you need to read individual files without sequentially iterating over the entire archive.
ZipEntry
An object representing an individual file or folder inside the archive. Contains name, size, date, flags, compression level, etc.
import java.util.zip.ZipEntry;
ZipEntry entry = new ZipEntry("docs/readme.txt");
entry.setComment("File description");
entry.setTime(System.currentTimeMillis());
Compression levels (Deflater)
When creating an archive, you can control the compression level (from 0—no compression—to 9—maximum compression):
import java.io.FileOutputStream;
import java.util.zip.Deflater;
import java.util.zip.ZipOutputStream;
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("archive.zip"))) {
zos.setLevel(Deflater.BEST_COMPRESSION); // or 0..9
// ...
}
- Deflater.NO_COMPRESSION (0)
- Deflater.BEST_SPEED (1)
- Deflater.BEST_COMPRESSION (9)
- Deflater.DEFAULT_COMPRESSION (-1)
Rule: the higher the level, the slower it is—but the better it compresses.
3. Compressing a single file
GZIP is a format for compressing a single file (not an archive!). It is used for logs, temporary files, and network transfer.
Example: compress a file
import java.util.zip.*;
import java.io.*;
try (GZIPOutputStream gos = new GZIPOutputStream(new FileOutputStream("file.txt.gz"));
FileInputStream fis = new FileInputStream("file.txt")) {
fis.transferTo(gos);
}
Example: decompress a file
import java.util.zip.GZIPInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
try (GZIPInputStream gis = new GZIPInputStream(new FileInputStream("file.txt.gz"));
FileOutputStream fos = new FileOutputStream("file.txt")) {
gis.transferTo(fos);
}
Remember: GZIP works with only one file—it does not preserve folder structure or additional metadata. In contrast, ZIP allows packing multiple files and entire folders, preserving their structure and information for each entry.
4. Packing/unpacking a directory, filters with PathMatcher
Packing a directory into a ZIP
To pack a folder with files and subfolders, traverse the file tree recursively and add each file to the archive with the correct relative path (separators in ZIP are always "/").
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
Path sourceDir = Paths.get("myfolder");
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("archive.zip"))) {
Files.walk(sourceDir)
.filter(Files::isRegularFile)
.forEach(path -> {
String entryName = sourceDir.relativize(path).toString().replace("\\", "/");
try (InputStream is = Files.newInputStream(path)) {
zos.putNextEntry(new ZipEntry(entryName));
is.transferTo(zos);
zos.closeEntry();
} catch (IOException e) { e.printStackTrace(); }
});
}
Unpacking an archive into a directory
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
try (ZipInputStream zis = new ZipInputStream(new FileInputStream("archive.zip"))) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
Path outPath = Paths.get("output", entry.getName());
if (entry.isDirectory()) {
Files.createDirectories(outPath);
} else {
Files.createDirectories(outPath.getParent());
try (OutputStream os = Files.newOutputStream(outPath)) {
zis.transferTo(os);
}
}
zis.closeEntry();
}
}
Filtering files by glob (PathMatcher)
You can filter files for packing/unpacking by a glob, for example only "*.txt":
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.PathMatcher;
PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:**/*.txt");
Files.walk(sourceDir)
.filter(matcher::matches)
.forEach(/* ... */);
5. Security: Zip Slip, zip bomb, path normalization checks
Zip Slip (path traversal)
Problem: An attacker can create an archive with a file whose name is "../../../../etc/passwd". When extracting without checks, such a file can overwrite system files!
Solution: before writing a file, normalize the path and make sure it does not escape the target directory.
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
Path targetDir = Paths.get("output").toAbsolutePath();
try (ZipInputStream zis = new ZipInputStream(new FileInputStream("archive.zip"))) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
Path outPath = targetDir.resolve(entry.getName()).normalize();
if (!outPath.startsWith(targetDir)) {
throw new IOException("Zip Slip: attempt to write outside the target directory!");
}
if (entry.isDirectory()) {
Files.createDirectories(outPath);
} else {
Files.createDirectories(outPath.getParent());
try (OutputStream os = Files.newOutputStream(outPath)) {
zis.transferTo(os);
}
}
zis.closeEntry();
}
}
Zip bomb
Problem: an archive can contain a file that, after extraction, takes gigabytes even though the archive itself is only a few kilobytes. This can “kill” a server or disk.
Solution: limit the maximum size of extracted files and the total extraction volume, aborting the process when a limit is exceeded.
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
long maxSize = 100 * 1024 * 1024; // 100 MB
long totalUnzipped = 0;
try (ZipInputStream zis = new ZipInputStream(new FileInputStream("archive.zip"))) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
Path outPath = Paths.get("output", entry.getName()).normalize();
Files.createDirectories(outPath.getParent());
long written = 0;
try (OutputStream os = Files.newOutputStream(outPath)) {
byte[] buf = new byte[8192];
int len;
while ((len = zis.read(buf)) > 0) {
os.write(buf, 0, len);
written += len;
totalUnzipped += len;
if (written > maxSize || totalUnzipped > maxSize) {
throw new IOException("Zip bomb detected!");
}
}
}
zis.closeEntry();
}
}
6. Practice: “zip/unzip” CLI with glob patterns
Let’s write a simple console utility for packing and unpacking files with glob support.
Example: packing
// java ZipUtil zip myfolder archive.zip *.txt
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public static void zip(String sourceDir, String zipFile, String glob) throws IOException {
Path src = Paths.get(sourceDir);
PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:" + glob);
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) {
Files.walk(src)
.filter(Files::isRegularFile)
.filter(matcher::matches)
.forEach(path -> {
String entryName = src.relativize(path).toString().replace("\\", "/");
try (InputStream is = Files.newInputStream(path)) {
zos.putNextEntry(new ZipEntry(entryName));
is.transferTo(zos);
zos.closeEntry();
} catch (IOException e) { e.printStackTrace(); }
});
}
}
Example: unpacking with Zip Slip protection
// java ZipUtil unzip archive.zip output
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public static void unzip(String zipFile, String outDir) throws IOException {
Path targetDir = Paths.get(outDir).toAbsolutePath();
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile))) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
Path outPath = targetDir.resolve(entry.getName()).normalize();
if (!outPath.startsWith(targetDir)) {
throw new IOException("Zip Slip: attempt to write outside the target directory!");
}
if (entry.isDirectory()) {
Files.createDirectories(outPath);
} else {
Files.createDirectories(outPath.getParent());
try (OutputStream os = Files.newOutputStream(outPath)) {
zis.transferTo(os);
}
}
zis.closeEntry();
}
}
}
Example run:
java ZipUtil zip myfolder archive.zip "*.txt"
java ZipUtil unzip archive.zip output
In the first example, the command java ZipUtil zip myfolder archive.zip "*.txt" packs all .txt files from the myfolder directory into the archive.zip archive. In the second example, java ZipUtil unzip archive.zip output extracts the archive into the output directory, while ensuring that no file is written outside the target directory—this is the protection against Zip Slip.
GO TO FULL VERSION