Creating ZIP and JAR Files in Java

ZIP files offer a packaging mechanism, allowing multiple files to be bundled together as one. Thus, when you need to download a group of files from the web, you can package them into one ZIP file for easier transport as a single file. The bundling can include additional information like directory hierarchy, thus preserving necessary paths for an application or series of resources once unbundled.

This tip will address three aspects of ZIP file usage:

  • creating them
  • adding files to them
  • compressing those added files

Creating Zip Streams

The ZipOutputStream offers a stream for compressing the outgoing bytes. There is a single constructor for ZipOutputStream, one that accepts another OutputStream:

public ZipOutputStream(OutputStream out)

If the constructor argument is the type FileOutputStream, then the compressed bytes written by the ZipOutputStream will be saved to a file. However, you aren’t limited to using the ZipOutputStream with a file. You can also use the OutputStream that comes from a socket connection or some other non-file-oriented stream. Thus, for a file-oriented ZipOutputStream, the typical usage will look like this:

String path = "afile.zip"; 
FileOutputStream fos = new FileOutputStream(path);
ZipOutputStream zos = new ZipOutputStream(fos);

Adding Entries

Once created, you don’t just write bytes to a ZipOutputStream. Instead, you need to treat the output stream as a collection of components. Each component of a ZipOutputStream is paired with a ZipEntry. It is this ZipEntry that you create and then add to the ZipOutputStream before actually writing its contents to the stream.

String name = ...; 
ZipEntry entry = new ZipEntry(name);
zos.putNextEntry(entry);
zos.write(<< all the bytes for entry >>);

Each entry serves as a marker in the overall stream, where you’ll find the bytes related to the entry in the library file. After the ZIP file has been created, when you need to get the entry contents back, just ask for the related input stream:

ZipFile zip = new ZipFile("afile.zip"); 
ZipEntry entry = zip.getEntry("anEntry.name");
InputStream is = zip.getInputStream(entry);

Now that you’ve seen how to create the zip file and add entries to that file, it is important to point out that the java.util.zip libraries offer some level of control for the added entries of the ZipOutputStream. First, the order you add entries to the ZipOutputStream is the order they are physically located in the .zip file. You can manipulate the enumeration of entries returned back by the entries() method of ZipFile to produce a list in alphabetical or size order, but the entries are still stored in the order they were written to the output stream.

Compressing Files

Files added to a ZIP/JAR file are compressed individually. Unlike Microsoft CAB files which compress the library package as a whole, files in a ZIP/JAR file are each compressed or not compressed separately. Before adding a ZipEntry to the ZipOutputStream, you determine whether its associated bytes are compressed. The setMethod method of ZipEntry allows you to specify which of the two available compression formats to use. Use the STORED constant of ZipEntry to give you an uncompressed file and the DEFLATED setting for a compressed version. You cannot control the compression efficiency. That depends on the type of data in the associated entry. Straight text can be compressed to around 80% of its size quite easily, whereas MP3 or JPEG data will be compressed much less.

While you might think it obvious that everything should be compressed, it does take time to compress and uncompress a file. If the task of compressing is too costly a task to do at the point of creation, it may sometimes be better to just store the data of the whole file in a STORED format, which just stores the raw bytes. The same can be said of the time cost of uncompression. Of course, uncompressed files are larger, and you have to pay the cost with either higher disk space usage or bandwidth when transferring file. Keep in mind that you need to change the setting for each entry, not the ZipFile as a whole. However, it is more typical to compress or not compress a whole ZipFile, as opposed to different settings for each entry.

There is one key thing you need to know if you use the STORED constant for the compression method: you must explicitly set certain attributes of the ZipEntry which are automatically set when the entry is compressed. These are the size, compressed size, and the checksum of the entry’s input stream. Assuming an input file, the size and compressed size can just be the file size. To compute the checksum, use the CRC32 class in the java.util.zip package. You cannot just pass in 0 or -1 to ignore the checksum value; the CRC value will be used to validate your input when creating the ZIP and when reading from it later.

ZipEntry entry = new ZipEntry(name);
entry.setMethod(ZipEntry.STORED);
entry.setCompressedSize(file.length());
entry.setSize(file.length());
CRC32 crc = new CRC32();
crc.update(<< all the bytes for entry >>); 
entry.setCrc(crc.getValue());
zos.putNextEntry(entry);

To demonstrate, the following program will combine a series of files using the STORED compression method. The first argument to the program will be the ZIP file to create. Remaining arguments represent the files to add. If the ZIP file to create already exists, the program will exit without modifying the file. If you add a non-existing file to the ZIP file, the program will skip the non-existing file, adding any remaining command line arguments to the created ZIP.

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

public class ZipIt {
    public static void main(String args[]) throws IOException {
        if (args.length < 2) {
            System.err.println("usage: java ZipIt Zip.zip file1 file2 file3");
            System.exit(-1);
        }
        File zipFile = new File(args[0]);
        if (zipFile.exists()) {
            System.err.println("Zip file already exists, please try another");
            System.exit(-2);
        }
        FileOutputStream fos = new FileOutputStream(zipFile);
        ZipOutputStream zos = new ZipOutputStream(fos);
        int bytesRead;
        byte[] buffer = new byte[1024];
        CRC32 crc = new CRC32();
        for (int i=1, n=args.length; i < n; i++) {
            String name = args[i];
            File file = new File(name);
            if (!file.exists()) {
                System.err.println("Skipping: " + name);
                continue;
            }
            BufferedInputStream bis = new BufferedInputStream(
                new FileInputStream(file));
            crc.reset();
            while ((bytesRead = bis.read(buffer)) != -1) {
                crc.update(buffer, 0, bytesRead);
            }
            bis.close();
            // Reset to beginning of input stream
            bis = new BufferedInputStream(
                new FileInputStream(file));
            ZipEntry entry = new ZipEntry(name);
            entry.setMethod(ZipEntry.STORED);
            entry.setCompressedSize(file.length());
            entry.setSize(file.length());
            entry.setCrc(crc.getValue());
            zos.putNextEntry(entry);
            while ((bytesRead = bis.read(buffer)) != -1) {
                zos.write(buffer, 0, bytesRead);
            }
            bis.close();
        }
        zos.close();
    }
}


11 Comments

  • magnetic 25 June, 2009, 17:55

    Very useful. Did not know this could be done. Thanks!!!!!

  • Viral Patel 26 June, 2009, 1:23

    Thanks for the comment Magnetic..

  • ramkumar 6 November, 2009, 21:58

    Hi Patel….Your blog is very useful…not only the information….the design also very attractive…..best wishes….keep going:)
    -P.Ramkumar

    • Viral Patel 6 November, 2009, 23:22

      Hi Ramkumar,
      Thanks for your comment. I hope you like the posts on the blog.

    • Domnic 6 September, 2012, 18:09

      Good post.. Thanks.. Keep it up..

  • ratna 15 May, 2012, 17:15

    Hi, I need your help.
    I used the same method to create zip file, It was working fine, but after the zip files size reached 226.344 KB, it will throw exception “java.lang.IllegalArgumentException”
    The problem is in this line zos.putNextEntry(entry)

    Thanks a lot

    • Viral Patel 15 May, 2012, 18:32

      @ratna – Not sure exactly why this error coming but check if the entry that is being added by zos.putNextEntry(entry) contains $ symbol (In filename or in file content) or any other special character. It seems that the putNextEntry() method is failing due to this. In case if it is, try to escape it as \$.

  • rapposa 21 August, 2012, 8:52

    sir, i am a newbie to java programming i m unable to run it in eclipse too.when i compiled the program it is displaying a message saying dat exception in main thread java.lang error.plz help me and as i am trying to update a jar file it is displaying message java.lang.NoclassDefounder:uf plz help me sir

  • Chad 26 June, 2013, 22:47

    Thanks for the info on the additional calls needed when calling ZipEntry.setMethod(STORED). This is not intuitive, and not in the Javadoc.

  • Vilakshan 14 July, 2013, 20:50

    I would like to know whether we can get the size of the individual files stored in the zip. Because initially, the zipEntry contains sizer & other details as -1. And on next read this are calculated properly. But for this one file gets skipped. I would like to have a solution because if I specify a maximum buffer size then the extracted files get filled with extra code bits. Which surely can not be allowed for a strict client based application.

  • ravi Kant gaur 5 August, 2013, 22:51

    Hi;

    i am using jdk 5 for my application and in the application I am zipping my files by using zip package classes of jdk 5.
    All things are going fine but where i am facing problem when my file name is in Non ASCII characters that ties it gives me some junk value for file name under zip folder.

    I know that for this I need to use jdk 7 new ZipOutputStream(object, charset) ; But I can’t change my whole application to jdk 7.

    So is there any other alternative or jar api you know through this I can zip my Non ASCII character value file name.

    please update me soon if possible.

Leave a Reply

Your email address will not be published. Required fields are marked *

Note

To post source code in comment, use [code language] [/code] tag, for example:

  • [code java] Java source code here [/code]
  • [code html] HTML here [/code]

Current day month ye@r *