280

I looked at the default Zip library that comes with the JDK and the Apache compression libs and I am unhappy with them for 3 reasons:

  1. They are bloated and have bad API design. I have to write 50 lines of boiler plate byte array output, zip input, file out streams and close relevant streams and catch exceptions and move byte buffers on my own? Why can't I have a simple API that looks like this Zipper.unzip(InputStream zipFile, File targetDirectory, String password = null) and Zipper.zip(File targetDirectory, String password = null) that just works?

  2. It seems zipping unzipping destroys file meta-data and password handling is broken.

  3. Also, all the libraries I tried were 2-3x slow compared to the command line zip tools I get with UNIX?

For me (2) and (3) are minor points but I really want a good tested library with a one-line interface.

pathikrit
  • 32,469
  • 37
  • 142
  • 221
  • 3
    AFAIK, the zip libraries Java uses are native code based on widely used libraries and the performance should be much the same. The performance difference could be the way you are using the data e.g. by taking copies of data before using it. – Peter Lawrey Feb 17 '12 at 08:44
  • 15
    As for #1, it's because not everybody is simply unzipping a file to a directory. If you're always using the same pattern, why not just write a utility class that wraps one of the others and does what you need it to and just use **that**? – Edward Thomson Feb 17 '12 at 14:15
  • 22
    @EdwardThomson because it's easier to use a library than to write code, test code, and maintain code. – Zak Aug 16 '13 at 10:12
  • 16
    @EdwardThomson: Your argument is invalid. Look at the Python zip API: http://docs.python.org/3/library/zipfile. You need 1 line of code to zip or unzip files. APIs should handle the common case very well and I cannot think of any use case of a zip API besides zipping or unzipping. – pathikrit Aug 23 '13 at 18:12
  • 8
    @wrick: zipping *a file* or unzipping *a file* is a special case of zipping or unzipping a stream. If your API doesn't let me write a stream to it and instead makes me write a stream to a file just so that I can feed that to your API, then your API is brain damaged. – Edward Thomson Aug 23 '13 at 18:51
  • 59
    @EdwardThomson - Fine, so make the library support both files and streams. It's a waste of everybody's time - mine, yours, the asker, and all the other Googlers who will stumble upon this that we each have to implement our own Zip Utilities. Just as there is DRY, there is DROP - Don't Repeat Other People. – ArtOfWarfare Nov 19 '13 at 18:23
  • Use Apache Commons Compress and ZipArchiveInputStream https://commons.apache.org/proper/commons-compress/zip.html - maven https://mvnrepository.com/artifact/org.apache.commons/commons-compress – kinjelom Mar 27 '18 at 08:57

9 Answers9

339

I know its late and there are lots of answers but this zip4j is one of the best libraries for zipping I have used. Its simple (no boiler code) and can easily handle password protected files.

import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.core.ZipFile;


public static void unzip(){
    String source = "some/compressed/file.zip";
    String destination = "some/destination/folder";
    String password = "password";

    try {
         ZipFile zipFile = new ZipFile(source);
         if (zipFile.isEncrypted()) {
            zipFile.setPassword(password);
         }
         zipFile.extractAll(destination);
    } catch (ZipException e) {
        e.printStackTrace();
    }
}

The Maven dependency is:

<dependency>
    <groupId>net.lingala.zip4j</groupId>
    <artifactId>zip4j</artifactId>
    <version>1.3.2</version>
</dependency>
user229044
  • 232,980
  • 40
  • 330
  • 338
user2003470
  • 3,793
  • 2
  • 14
  • 13
  • 1
    There can also be a problem when you unzip file that is in resources folder. You take a zip file like that `code new File(getClass().getResource(zipFileName).getPath());` But then that file won't be unzipped and it will lead to EOFException or MALFORMED. That is all because when you use maven you must to turn off filtering in maven resources plugin zip ... – Евгений Коптюбенко Mar 07 '19 at 09:52
  • for version 2.11.3 to up use `import net.lingala.zip4j.ZipFile` [ref](https://javadoc.io/doc/net.lingala.zip4j/zip4j/2.2.4/net/lingala/zip4j/ZipFile.html) – KuhakuPixel Feb 25 '23 at 12:38
  • Cannot unzip from InputStream, only from FIle. That is problem for example when using resources. – Daniel Hári May 20 '23 at 20:04
98

In Java 8, with Apache Commons-IO's IOUtils you can do this:

try (java.util.zip.ZipFile zipFile = new ZipFile(file)) {
  Enumeration<? extends ZipEntry> entries = zipFile.entries();
  while (entries.hasMoreElements()) {
    ZipEntry entry = entries.nextElement();
    File entryDestination = new File(outputDir,  entry.getName());
    if (entry.isDirectory()) {
        entryDestination.mkdirs();
    } else {
        entryDestination.getParentFile().mkdirs();
        try (InputStream in = zipFile.getInputStream(entry);
             OutputStream out = new FileOutputStream(entryDestination)) {
            IOUtils.copy(in, out);
        }
    }
  }
}

It's still some boilerplate code, but it has only 1 non-exotic dependency: Commons-IO

In Java 11 and higher, better options might be available, see ZhekaKozlov's comment.

Geoffrey De Smet
  • 26,223
  • 11
  • 73
  • 120
  • 1
    @VitalySazanovich you're referring to the Java 7 ZipEntry. – Randy Mar 19 '14 at 10:23
  • 4
    why not IOUtils.closeQuietly(out)? – Juan Mendez Jul 22 '15 at 15:31
  • 2
    @JuanMendez because if there are errors on close, you can't be sure the file was saved entirely and correctly. But additionally to the normal `close()` it won't hurt. – vadipp Jun 14 '16 at 09:42
  • Unfortunately, this code does not work with newer versions of apache compress library. – Mader Levap Dec 11 '18 at 15:09
  • 4
    This solution is vulnerable to [ZipSlip](https://snyk.io/research/zip-slip-vulnerability) (zip4j is also [affected](https://snyk.io/vuln/SNYK-JAVA-NETLINGALAZIP4J-31679)) – Marcono1234 Apr 27 '19 at 17:54
  • 4
    In Java 9+, you don't need IOUtils anymore. Just write `zipFile.getInputStream(entry).transferTo(outputStream)`. – ZhekaKozlov Feb 09 '21 at 10:01
  • If my understanding is correct, ZipSlip be avoided when this solution is combined with the `newFile()` from this [Baeldung post](https://www.baeldung.com/java-compress-and-uncompress#unzip). This solution and the one from the post are quite similar, but this one is a bit more modern. – gillesB Sep 07 '22 at 19:36
47

Extract zip file and all its subfolders, using only the JDK:

private void extractFolder(String zipFile,String extractFolder) 
{
    try
    {
        int BUFFER = 2048;
        File file = new File(zipFile);

        ZipFile zip = new ZipFile(file);
        String newPath = extractFolder;

        new File(newPath).mkdir();
        Enumeration zipFileEntries = zip.entries();

        // Process each entry
        while (zipFileEntries.hasMoreElements())
        {
            // grab a zip file entry
            ZipEntry entry = (ZipEntry) zipFileEntries.nextElement();
            String currentEntry = entry.getName();

            File destFile = new File(newPath, currentEntry);
            //destFile = new File(newPath, destFile.getName());
            File destinationParent = destFile.getParentFile();

            // create the parent directory structure if needed
            destinationParent.mkdirs();

            if (!entry.isDirectory())
            {
                BufferedInputStream is = new BufferedInputStream(zip
                .getInputStream(entry));
                int currentByte;
                // establish buffer for writing file
                byte data[] = new byte[BUFFER];

                // write the current file to disk
                FileOutputStream fos = new FileOutputStream(destFile);
                BufferedOutputStream dest = new BufferedOutputStream(fos,
                BUFFER);

                // read and write until last byte is encountered
                while ((currentByte = is.read(data, 0, BUFFER)) != -1) {
                    dest.write(data, 0, currentByte);
                }
                dest.flush();
                dest.close();
                is.close();
            }


        }
    }
    catch (Exception e) 
    {
        Log("ERROR: "+e.getMessage());
    }

}

Zip files and all its subfolders:

 private void addFolderToZip(File folder, ZipOutputStream zip, String baseName) throws IOException {
    File[] files = folder.listFiles();
    for (File file : files) {
        if (file.isDirectory()) {
            addFolderToZip(file, zip, baseName);
        } else {
            String name = file.getAbsolutePath().substring(baseName.length());
            ZipEntry zipEntry = new ZipEntry(name);
            zip.putNextEntry(zipEntry);
            IOUtils.copy(new FileInputStream(file), zip);
            zip.closeEntry();
        }
    }
}
rogerdpack
  • 62,887
  • 36
  • 269
  • 388
Bashir Beikzadeh
  • 761
  • 8
  • 15
  • 9
    The calls to close should be inside "finally" blocks at the very least. Exceptions are not handled well. -> I guess that's part of why the OP asked for a _library_ to use. –  Aug 24 '12 at 10:40
  • 1
    This code does not keep file attributes and permissions... if you use something like this to unzip a runnable application, be prepared for weird errors regarding file permissions. This has cost me a week of headache. – Renato Apr 18 '20 at 19:01
25

Another option that you can check out is zt-zip available from Maven central and project page at https://github.com/zeroturnaround/zt-zip

It has the standard packing and unpacking functionality (on streams and on filesystem) + lots of helper methods to test for files in an archive or add/remove entries.

toomasr
  • 4,731
  • 2
  • 33
  • 36
21

Full Implementation to Zip/Unzip a Folder/File with zip4j


Add this dependency to your build manager. Or, download the latest JAR file from here and add it to your project build path. The class bellow can compress and extract any file or folder with or without password protection-

import java.io.File;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.util.Zip4jConstants;
import net.lingala.zip4j.core.ZipFile;  

public class Compressor {
    public static void zip (String targetPath, String destinationFilePath, String password) {
        try {
            ZipParameters parameters = new ZipParameters();
            parameters.setCompressionMethod(Zip4jConstants.COMP_DEFLATE);
            parameters.setCompressionLevel(Zip4jConstants.DEFLATE_LEVEL_NORMAL);

            if (password.length() > 0) {
                parameters.setEncryptFiles(true);
                parameters.setEncryptionMethod(Zip4jConstants.ENC_METHOD_AES);
                parameters.setAesKeyStrength(Zip4jConstants.AES_STRENGTH_256);
                parameters.setPassword(password);
            }
                
            ZipFile zipFile = new ZipFile(destinationFilePath);
                
            File targetFile = new File(targetPath);
            if (targetFile.isFile()) {
                zipFile.addFile(targetFile, parameters);
            } else if (targetFile.isDirectory()) {
                zipFile.addFolder(targetFile, parameters);
            } else {
                //neither file nor directory
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
        
    public static void unzip(String targetZipFilePath, String destinationFolderPath, String password) {
        try {
            ZipFile zipFile = new ZipFile(targetZipFilePath);
            if (zipFile.isEncrypted()) {
                zipFile.setPassword(password);
            }
            zipFile.extractAll(destinationFolderPath);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**/ /// for test
    public static void main(String[] args) {
        
        String targetPath = "target\\file\\or\\folder\\path";
        String zipFilePath = "zip\\file\\Path"; 
        String unzippedFolderPath = "destination\\folder\\path";
        String password = "your_password"; // keep it EMPTY<""> for applying no password protection
            
        Compressor.zip(targetPath, zipFilePath, password);
        Compressor.unzip(zipFilePath, unzippedFolderPath, password);
    }/**/
}

For more detailed usage, please see here.

Minhas Kamal
  • 20,752
  • 7
  • 62
  • 64
  • 2
    A nice answer and library. Extracting 1868 files took ~15 seconds on this library, compared to 20+ minutes when using ZipInputStream (for some reason) – Jonty800 Mar 26 '18 at 16:28
  • @Jonty800 With performance differences like that you should maybe take a second look at your implementation. If you don't buffer your streams and every single byte is read/written directly from the device, then you will get such performance differences. I just extracted 17588 files with a total size of 1.8 GB and zip4j took 64 seconds while a buffered standard library implementation took 39 seconds. That being said a naive BufferedOutputStream implementation took around 5 minutes. – Felix S May 06 '21 at 11:05
8

A very nice project is TrueZip.

TrueZIP is a Java based plug-in framework for virtual file systems (VFS) which provides transparent access to archive files as if they were just plain directories

For example (from the website):

File file = new TFile("archive.tar.gz/README.TXT");
OutputStream out = new TFileOutputStream(file);
try {
   // Write archive entry contents here.
   ...
} finally {
   out.close();
}
Arnost Valicek
  • 2,418
  • 1
  • 18
  • 14
Michael
  • 4,722
  • 6
  • 37
  • 58
  • The library looks nice - still its not obvious how to simply unzip a zip-file given a zipinputstream/file/path. – pathikrit Feb 17 '12 at 13:19
  • 1
    TrueZIP doesn't seem to handle reading from streams very well. – Teo Klestrup Röijezon Jun 05 '13 at 21:31
  • 5
    Isn't it largely the same as what you can do in Java 7 ? (look at [ZipFileSystemProvider](http://docs.oracle.com/javase/7/docs/technotes/guides/io/fsp/zipfilesystemprovider.html)). – peterh Mar 18 '14 at 09:47
  • 1
    @peterh: The standard-JDK ZipFileSystemProvider would be a good answer. Only few people see it as comment. – iuzuz Oct 09 '18 at 15:20
4

Another option is JZlib. In my experience, it's less "file-centered" than zip4J, so if you need to work on in-memory blobs rather than files, you may want to take a look at it.

Henrik Aasted Sørensen
  • 6,966
  • 11
  • 51
  • 60
0

There's a full example here for zipping and unzipping files recursively: http://developer-tips.hubpages.com/hub/Zipping-and-Unzipping-Nested-Directories-in-Java-using-Apache-Commons-Compress

user1491819
  • 1,790
  • 15
  • 20
0

Did you have a look at http://commons.apache.org/vfs/ ? It claims to simplify a lot of things for you. But I've never used it in a project.

I also am not aware of Java-Native compression libs other than the JDK or Apache Compression.

I remember once we ripped some features out of Apache Ant - they have a lot of utils for compression / decompression built in.

Sample code with VFS would look like:

File zipFile = ...;
File outputDir = ...;
FileSystemManager fsm = VFS.getManager();
URI zip = zipFile.toURI();
FileObject packFileObject = fsm.resolveFile(packLocation.toString());
FileObject to = fsm.toFileObject(destDir);
FileObject zipFS;
try {
    zipFS = fsm.createFileSystem(packFileObject);
    fsm.toFileObject(outputDir).copyFrom(zipFS, new AllFileSelector());
} finally {
    zipFS.close();
}
eckes
  • 10,103
  • 1
  • 59
  • 71
wemu
  • 7,952
  • 4
  • 30
  • 59
  • 1
    Looks like the support for zip files in the VFS stuff on its own is quite limited: http://commons.apache.org/vfs/filesystems.html – T.J. Crowder Feb 17 '12 at 08:37