Skip to content
Java file handling 6 min read

Create & Read Zip Files

Working with ZIP archives is a common task — bundling multiple files for download, compressing logs, or packaging resources. Java’s built-in java.util.zip package gives you everything you need to create and read ZIP files without any external library.

The Core Classes

Java’s ZIP support lives in java.util.zip. You will use two classes almost exclusively:

ClassPurpose
ZipOutputStreamWraps an OutputStream to write a ZIP archive
ZipInputStreamWraps an InputStream to read/extract a ZIP archive
ZipEntryRepresents a single file (or directory) inside the archive
ZipFileRandom-access reading of a ZIP archive by entry name

Note: ZipOutputStream and ZipInputStream follow the standard Java I/O decorator pattern — they wrap any OutputStream / InputStream, so you can zip to a file, a byte array, or even a network socket.

Creating a ZIP File

To create a ZIP archive you:

  1. Open a FileOutputStream pointing at the target .zip file.
  2. Wrap it in a ZipOutputStream.
  3. For each file to add, create a ZipEntry, call putNextEntry(), copy the file bytes, then call closeEntry().
import java.io.*;
import java.util.zip.*;

public class CreateZip {
    public static void main(String[] args) throws IOException {
        // Files to compress (must already exist)
        String[] files = {"hello.txt", "data.csv"};

        try (ZipOutputStream zos = new ZipOutputStream(
                new FileOutputStream("archive.zip"))) {

            for (String fileName : files) {
                File file = new File(fileName);
                try (FileInputStream fis = new FileInputStream(file)) {
                    // Each entry represents one file inside the ZIP
                    ZipEntry entry = new ZipEntry(file.getName());
                    zos.putNextEntry(entry);

                    byte[] buffer = new byte[1024];
                    int length;
                    while ((length = fis.read(buffer)) >= 0) {
                        zos.write(buffer, 0, length);
                    }
                    zos.closeEntry();
                }
            }
        }
        System.out.println("archive.zip created successfully.");
    }
}

Output:

archive.zip created successfully.

Tip: Always use try-with-resources. Closing a ZipOutputStream flushes the central directory record to the end of the file — if you skip close(), the ZIP will be corrupt.

Preserving Directory Structure

If you want entries like reports/2024/summary.txt inside the ZIP, just use a path string as the entry name:

// Entry name controls the path inside the archive
ZipEntry entry = new ZipEntry("reports/2024/summary.txt");
zos.putNextEntry(entry);
// ... write bytes ...
zos.closeEntry();

The ZIP format stores the path as part of the entry metadata — no actual directories are created on disk inside the archive unless you explicitly add directory entries (entries whose names end with /).

Adding an Explicit Directory Entry

// A directory entry has a trailing slash and zero bytes
ZipEntry dirEntry = new ZipEntry("reports/2024/");
zos.putNextEntry(dirEntry);
zos.closeEntry(); // no bytes to write

Setting Compression Level

By default ZipOutputStream uses Deflate compression. You can control the compression level (0 = no compression, 9 = maximum) with setLevel():

import java.io.*;
import java.util.zip.*;

public class CompressedZip {
    public static void main(String[] args) throws IOException {
        try (ZipOutputStream zos = new ZipOutputStream(
                new FileOutputStream("compressed.zip"))) {

            zos.setLevel(Deflater.BEST_COMPRESSION); // level 9

            ZipEntry entry = new ZipEntry("notes.txt");
            zos.putNextEntry(entry);
            zos.write("Hello, compressed world!".getBytes());
            zos.closeEntry();
        }
        System.out.println("Done.");
    }
}

You can also store entries without compression using ZipEntry.STORED, but you must then set the entry’s size, compressedSize, and crc manually before calling putNextEntry() — usually not worth the hassle unless you need random-access speed.

Reading (Extracting) a ZIP File

Use ZipInputStream to iterate through entries one by one:

import java.io.*;
import java.util.zip.*;

public class ReadZip {
    public static void main(String[] args) throws IOException {
        try (ZipInputStream zis = new ZipInputStream(
                new FileInputStream("archive.zip"))) {

            ZipEntry entry;
            while ((entry = zis.getNextEntry()) != null) {
                System.out.println("Extracting: " + entry.getName()
                        + "  (" + entry.getSize() + " bytes)");

                // Write each entry out to disk
                try (FileOutputStream fos = new FileOutputStream(entry.getName())) {
                    byte[] buffer = new byte[1024];
                    int length;
                    while ((length = zis.read(buffer)) >= 0) {
                        fos.write(buffer, 0, length);
                    }
                }
                zis.closeEntry();
            }
        }
    }
}

Output:

Extracting: hello.txt  (26 bytes)
Extracting: data.csv  (142 bytes)

Warning: Before writing extracted files to disk, always sanitize entry.getName() to prevent Zip Slip — a path-traversal vulnerability where a malicious archive contains entries like ../../etc/passwd. Check that the resolved path stays within your target directory.

Zip Slip Prevention

File targetDir = new File("output/");
File outFile = new File(targetDir, entry.getName()).getCanonicalFile();

if (!outFile.toPath().startsWith(targetDir.getCanonicalFile().toPath())) {
    throw new SecurityException("Zip Slip detected: " + entry.getName());
}

Random-Access Reading with ZipFile

ZipInputStream reads entries sequentially. When you need to jump directly to a named entry, use ZipFile instead:

import java.io.*;
import java.util.zip.*;

public class RandomAccessZip {
    public static void main(String[] args) throws IOException {
        try (ZipFile zipFile = new ZipFile("archive.zip")) {
            // Get a specific entry by name
            ZipEntry entry = zipFile.getEntry("hello.txt");
            if (entry != null) {
                try (InputStream is = zipFile.getInputStream(entry);
                     BufferedReader reader = new BufferedReader(
                             new InputStreamReader(is))) {
                    String line;
                    while ((line = reader.readLine()) != null) {
                        System.out.println(line);
                    }
                }
            }
        }
    }
}

ZipFile reads the central directory (stored at the end of the ZIP) into memory first, giving O(1) lookup by name rather than O(n) sequential scan.

Listing All Entries Without Extracting

import java.util.zip.*;

public class ListZip {
    public static void main(String[] args) throws IOException {
        try (ZipFile zipFile = new ZipFile("archive.zip")) {
            zipFile.entries().asIterator().forEachRemaining(entry ->
                System.out.printf("%-30s %8d bytes%n",
                    entry.getName(), entry.getSize())
            );
        }
    }
}

Output:

hello.txt                           26 bytes
data.csv                           142 bytes

Under the Hood

ZIP Format Structure

A ZIP file is not simply a sequence of compressed streams — it has a specific binary structure:

  • Local file headers — appear before each entry’s compressed data, holding the file name, compression method, sizes, and CRC-32.
  • Central directory — a catalogue of all entries written at the very end of the file. This is why ZipFile can do fast random access without scanning the whole archive.
  • End of central directory record (EOCD) — a small fixed-size record after the central directory that tells readers where the central directory starts.

ZipInputStream reads local headers sequentially (it never sees the central directory). ZipFile seeks to the EOCD first, loads the central directory, and builds an in-memory index.

Deflate Compression

The default DEFLATED method is the same algorithm used in gzip and PNG. It combines LZ77 (back-references to earlier data) with Huffman coding (shorter codes for frequent symbols). Java delegates to the native zlib library via JNI, so compression and decompression are fast even for large files.

Buffering Tip

Wrap your FileInputStream / FileOutputStream in a BufferedInputStream or BufferedOutputStream when dealing with many small files. Each zos.write() call eventually hits the underlying stream; buffering reduces system-call overhead significantly.

// Buffered for better performance
try (ZipOutputStream zos = new ZipOutputStream(
        new BufferedOutputStream(new FileOutputStream("archive.zip")))) {
    // ...
}

NIO.2 Alternative

Java 7+ ships with a ZipFileSystem accessible through the NIO.2 API (java.nio.file), letting you treat a ZIP archive as a virtual filesystem:

import java.net.URI;
import java.nio.file.*;

public class NioZip {
    public static void main(String[] args) throws Exception {
        URI uri = URI.create("jar:file:/tmp/archive.zip");
        try (FileSystem fs = FileSystems.newFileSystem(uri,
                java.util.Map.of("create", "true"))) {

            Path inside = fs.getPath("/hello.txt");
            Files.writeString(inside, "Hello from NIO.2!");
        }
        System.out.println("Written via NIO.2 ZipFileSystem.");
    }
}

This approach integrates seamlessly with Files.copy(), Files.walk(), and other NIO.2 utilities — see NIO.2: Path & Files for more.

  • NIO.2: Path & Files — modern file operations that integrate with ZipFileSystem for treating archives as virtual directories
  • BufferedInputStream — wrap your streams in a buffer to dramatically speed up ZIP creation for many small files
  • BufferedOutputStream — buffer the output side when writing ZIP archives to disk
  • File Handling — the broader context of reading, writing, and managing files in Java
  • Serialization — another approach to persisting Java objects, often combined with ZIP streams for compact storage
  • Java I/O — overview of Java’s entire I/O framework and where ZIP streams fit in
Last updated June 13, 2026
Was this helpful?