Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Java ZIP entry extraction: cap expanded bytes while reading

Last updated: 5 Oct 20264 min read
tutorial
IntermediateBy AITrove Editorial

A ZIP entry can expand far beyond its compressed size, so enforce a decompressed-byte limit during extraction.

Count actual output

Metadata such as an entry's compressed size is not a safe allocation or acceptance limit. Count bytes returned by ZipInputStream.read and stop as soon as a configured expanded limit is exceeded. The fixture creates its own in-memory ZIP; its extraction path rejects a 47-byte entry under a 32-byte cap.

Path traversal is a different boundary. If extracting to disk, validate destination paths as described in ZIP path validation and decide how to clean up a partial file after rejection.

Budget the whole archive

A per-entry cap alone permits many entries to consume unlimited total output. Add entry count, aggregate expanded bytes, compression ratio or CPU budget where appropriate. Read incrementally; a bounded copy loop avoids materializing the entire expanded entry in memory.

Working program

Java
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

public class ArchiveExpansionGate {
    static byte[] archive(int payloadLength) throws IOException {
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        try (ZipOutputStream zip = new ZipOutputStream(bytes)) {
            zip.putNextEntry(new ZipEntry("inventory.txt"));
            for (int index = 0; index < payloadLength; index++) zip.write('X');
            zip.closeEntry();
        }
        return bytes.toByteArray();
    }

    static int expandedSize(byte[] archive, int maximum) throws IOException {
        try (ZipInputStream zip = new ZipInputStream(new ByteArrayInputStream(archive))) {
            if (zip.getNextEntry() == null) throw new IOException("missing entry");
            byte[] chunk = new byte[16];
            int total = 0;
            int count;
            while ((count = zip.read(chunk)) != -1) {
                if (count > maximum - total) throw new IOException("expanded limit exceeded");
                total += count;
            }
            return total;
        }
    }

    public static void main(String[] args) throws IOException {
        System.out.println(expandedSize(archive(12), 32));
        try { expandedSize(archive(47), 32); }
        catch (IOException rejected) { System.out.println(rejected.getMessage()); }
    }
}

Output

Output
12
expanded limit exceeded

Cost and ownership

The extractor uses a 16-byte chunk and constant working memory per entry. Counting is O(n) in expanded bytes read until rejection. The in-memory test archive itself uses O(n) storage only to keep this standalone program runnable.

Common Mistakes

  • Do not trust ZIP size metadata as the only limit.
  • Do not omit an aggregate archive budget.
  • Do not treat a byte cap as protection against unsafe destination paths.

Read next

Java ZIP inputs: bounded staging and rejected path traversal, Java byte streams: partial reads and bounded copying, Java InputStream short reads: assemble a complete record header, Java Files.move: atomic publication is a filesystem contract.

Continue with: Java GZIPInputStream: cap bytes after decompression, Java ZIP CRC32: detect corruption without claiming authenticity.

java
io
zip-entry-expanded-limit
Storage details