Java ZIP and GZIP Compression: ZipOutputStream, GZIPOutputStream
Java's java.util.zip package provides everything needed to create and read ZIP archives and GZIP-compressed files. ZipOutputStream handles multi-file archives with ZipEntry metadata (name, comment, timestamp, compression level). GZIPOutputStream compresses a single stream: ideal for HTTP compression or compressed log files. Java also supports treating a ZIP file as a FileSystem via NIO for in-place reading and writing.
Contents
Creating a ZIP Archive
ZipOutputStreamOutputStreamputNextEntry(new ZipEntry(name))closeEntry()ZipOutputStreamFileOutputStreamBufferedOutputStream09
import java.io.*;
import java.util.zip.*;
import java.nio.file.*;
// Create a ZIP with multiple files
try (ZipOutputStream zos = new ZipOutputStream(
new BufferedOutputStream(new FileOutputStream("archive.zip")))) {
// Set compression level (0=store, 9=best compression)
zos.setLevel(Deflater.BEST_COMPRESSION); // default is 6
// Add a file entry from disk
addFileToZip(zos, Path.of("report.csv"), "data/report.csv");
addFileToZip(zos, Path.of("summary.txt"), "summary.txt");
// Add an in-memory entry (no source file needed)
ZipEntry memEntry = new ZipEntry("meta/info.json");
memEntry.setComment("Metadata file");
zos.putNextEntry(memEntry);
zos.write("{\"version\": 1}".getBytes(StandardCharsets.UTF_8));
zos.closeEntry();
}
void addFileToZip(ZipOutputStream zos, Path file, String entryName) throws IOException {
ZipEntry entry = new ZipEntry(entryName);
// Optional: set modification time
entry.setLastModifiedTime(Files.getLastModifiedTime(file));
zos.putNextEntry(entry);
Files.copy(file, zos); // efficient — streams file bytes directly into zip
zos.closeEntry();
}
// Store mode — add file without compression (useful for already-compressed content)
ZipEntry stored = new ZipEntry("image.png");
stored.setMethod(ZipEntry.STORED);
stored.setSize(Files.size(imagePath));
stored.setCrc(computeCrc(imagePath)); // required for STORED entries
zos.putNextEntry(stored);
Files.copy(imagePath, zos);
zos.closeEntry();
Reading a ZIP Archive
ZipInputStreamFileInputStreamgetNextEntry()ZipEntrynullcloseEntry()ZipFile
import java.util.zip.*;
// Read all entries from a ZIP file
try (ZipInputStream zis = new ZipInputStream(
new BufferedInputStream(new FileInputStream("archive.zip")))) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
if (entry.isDirectory()) {
System.out.println("DIR: " + entry.getName());
zis.closeEntry();
continue;
}
System.out.printf("FILE: %-30s size=%d%n", entry.getName(), entry.getSize());
// Extract to disk
Path outPath = Path.of("extracted", entry.getName());
Files.createDirectories(outPath.getParent());
Files.copy(zis, outPath, StandardCopyOption.REPLACE_EXISTING);
// Note: do NOT close zis between entries — just closeEntry()
zis.closeEntry();
}
}
// ZipFile — random access to entries by name (doesn't need sequential iteration)
try (ZipFile zf = new ZipFile("archive.zip")) {
System.out.println("Total entries: " + zf.size());
// Access a specific entry by name
ZipEntry entry = zf.getEntry("data/report.csv");
if (entry != null) {
try (InputStream in = zf.getInputStream(entry)) {
String content = new String(in.readAllBytes(), StandardCharsets.UTF_8);
System.out.println(content);
}
}
// List all entries
zf.entries().asIterator().forEachRemaining(e ->
System.out.println(e.getName() + " (" + e.getCompressedSize() + " bytes)"));
}
../../etc/passwdGZIP Single-file Compression
GZIP is a single-stream compression format: it compresses one byte sequence, not a collection of named files like ZIP. GZIPOutputStreamGZIPInputStream.gz
import java.util.zip.*;
// Compress a file with GZIP
void gzipFile(String src, String dst) throws IOException {
try (InputStream in = new BufferedInputStream(new FileInputStream(src));
OutputStream out = new GZIPOutputStream(new FileOutputStream(dst))) {
in.transferTo(out); // Java 9+ — copies all bytes
}
}
// Decompress a GZIP file
void gunzipFile(String src, String dst) throws IOException {
try (InputStream in = new GZIPInputStream(new FileInputStream(src));
OutputStream out = new BufferedOutputStream(new FileOutputStream(dst))) {
in.transferTo(out);
}
}
// In-memory GZIP — compress a String to byte[]
byte[] compress(String text) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (GZIPOutputStream gz = new GZIPOutputStream(baos)) {
gz.write(text.getBytes(StandardCharsets.UTF_8));
}
return baos.toByteArray();
}
// Decompress byte[] back to String
String decompress(byte[] compressed) throws IOException {
try (GZIPInputStream gz = new GZIPInputStream(new ByteArrayInputStream(compressed))) {
return new String(gz.readAllBytes(), StandardCharsets.UTF_8);
}
}
// Usage: compress HTTP request/response bodies
byte[] compressedBody = compress(jsonResponse);
System.out.println("Original: " + jsonResponse.length() + " bytes");
System.out.println("Compressed: " + compressedBody.length + " bytes");
System.out.println("Ratio: " + (100 - 100 * compressedBody.length / jsonResponse.length()) + "% smaller");
NIO ZipFileSystem
FileSystems.newFileSystem()FilesFiles.readAllLines()Files.writeString()Files.walk()FileSystemMap.of("create", "true")
import java.nio.file.*;
// ZipFileSystem — treat a ZIP as a filesystem; read/write in-place
// No need to extract first
// Open existing ZIP
try (FileSystem fs = FileSystems.newFileSystem(Path.of("archive.zip"))) {
// List all files in the ZIP
Files.walk(fs.getPath("/"))
.filter(p -> !Files.isDirectory(p))
.forEach(System.out::println);
// Read a file inside the ZIP
Path entry = fs.getPath("/data/report.csv");
List<String> lines = Files.readAllLines(entry, StandardCharsets.UTF_8);
// Write/overwrite a file inside the ZIP
Path meta = fs.getPath("/meta/info.json");
Files.writeString(meta, "{\"version\": 2}");
}
// Changes are committed when the FileSystem is closed
// Create a new ZIP using ZipFileSystem
Map<String, String> env = Map.of("create", "true");
try (FileSystem fs = FileSystems.newFileSystem(
URI.create("jar:file:/tmp/new.zip"), env)) {
// Create directories and files inside the new ZIP
Files.createDirectory(fs.getPath("/data"));
Files.writeString(fs.getPath("/data/hello.txt"), "Hello, ZIP!");
Files.copy(Path.of("logo.png"), fs.getPath("/logo.png"));
}
// /tmp/new.zip created with the specified structure
ZipFileSystemFileSystemPractical Patterns
Recurring ZIP use cases include compressing HTTP responses on the fly, archiving log directories for rotation, and packaging deployment artifacts. Always close streams in try-with-resources: an unclosed ZipOutputStreamFiles.copy(path, zipOutputStream)../
// Pattern 1: Compress a directory recursively into a ZIP
void zipDirectory(Path sourceDir, Path zipFile) throws IOException {
try (ZipOutputStream zos = new ZipOutputStream(
new BufferedOutputStream(Files.newOutputStream(zipFile)))) {
Files.walk(sourceDir)
.filter(p -> !Files.isDirectory(p))
.forEach(p -> {
try {
String entryName = sourceDir.relativize(p).toString()
.replace(File.separatorChar, '/'); // ZIP uses /
zos.putNextEntry(new ZipEntry(entryName));
Files.copy(p, zos);
zos.closeEntry();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
}
// Pattern 2: Read a ZIP file's manifest (if it has one — e.g., a JAR)
try (ZipFile jar = new ZipFile("app.jar")) {
ZipEntry manifest = jar.getEntry("META-INF/MANIFEST.MF");
if (manifest != null) {
String content = new String(jar.getInputStream(manifest).readAllBytes());
System.out.println(content);
}
}
// Pattern 3: Zip Slip prevention
Path targetDir = Path.of("output").toAbsolutePath().normalize();
ZipEntry entry = new ZipEntry("../../etc/passwd"); // malicious
Path resolved = targetDir.resolve(entry.getName()).normalize();
if (!resolved.startsWith(targetDir)) {
throw new SecurityException("Zip Slip detected: " + entry.getName());
}