All formats

Apache Commons Compress

LanguageJava (8 and later)
LicenseApache 2.0
Repositorygithub.com/apache/commons-compress
DocumentationTAR package docs, javadoc
Depends oncommons-io, commons-lang3; optional org.tukaani:xz for xz, com.github.luben:zstd-jni for zstd
Read / writeyes / yes; ustar, pax, GNU, star; gzip, bzip2, xz, zstd, lzma and others through compressor streams

Commons Compress is the Apache library for archive and compression formats in Java: TAR, ZIP, 7z, cpio, ar, plus gzip, bzip2, xz, zstd, LZ4 and Brotli (read). For TAR it has:

By default the output stream fails on names over 100 bytes and files over 8 GiB, to stay compatible with old ustar readers. Set LONGFILE_POSIX and BIGNUMBER_POSIX to write pax headers instead.

Installation#

Maven:

XML
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-compress</artifactId>
  <version>1.28.0</version>
</dependency>
<!-- only for .tar.xz -->
<dependency>
  <groupId>org.tukaani</groupId>
  <artifactId>xz</artifactId>
  <version>1.12</version>
</dependency>

Gradle:

implementation("org.apache.commons:commons-compress:1.28.0")

Basic usage: list and extract a .tar.gz#

Java
import java.io.*;
import java.nio.file.*;
import java.util.zip.GZIPInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;

public class Untar {
    public static void main(String[] args) throws IOException {
        Path dest = Paths.get("out").toAbsolutePath().normalize();
        try (InputStream fi = Files.newInputStream(Paths.get("archive.tar.gz"));
             InputStream gz = new GZIPInputStream(new BufferedInputStream(fi));
             TarArchiveInputStream tar = new TarArchiveInputStream(gz)) {
            TarArchiveEntry entry;
            while ((entry = tar.getNextEntry()) != null) {
                System.out.printf("%10d %s%n", entry.getSize(), entry.getName());
                Path target = dest.resolve(entry.getName()).normalize();
                if (!target.startsWith(dest)) {
                    throw new IOException("entry outside target dir: " + entry.getName());
                }
                if (entry.isDirectory()) {
                    Files.createDirectories(target);
                } else if (entry.isFile()) {
                    Files.createDirectories(target.getParent());
                    Files.copy(tar, target, StandardCopyOption.REPLACE_EXISTING);  // reads only this entry
                }
            }
        }
    }
}

How to create a .tar.gz with long file names#

Java
import java.io.*;
import java.nio.file.*;
import java.util.stream.Stream;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;

public class MakeTar {
    public static void main(String[] args) throws IOException {
        Path src = Paths.get("docs");
        try (OutputStream fo = Files.newOutputStream(Paths.get("docs.tar.gz"));
             OutputStream gz = new GzipCompressorOutputStream(new BufferedOutputStream(fo));
             TarArchiveOutputStream tar = new TarArchiveOutputStream(gz);
             Stream<Path> files = Files.walk(src)) {
            tar.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);   // pax for names > 100 bytes
            tar.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_POSIX); // pax for files > 8 GiB
            for (Path p : (Iterable<Path>) files::iterator) {
                String name = src.getParent() == null
                        ? p.toString() : src.getParent().relativize(p).toString();
                TarArchiveEntry entry = tar.createArchiveEntry(p, name.replace('\\', '/'));
                tar.putArchiveEntry(entry);
                if (Files.isRegularFile(p)) {
                    Files.copy(p, tar);
                }
                tar.closeArchiveEntry();
            }
            tar.finish();  // end-of-archive blocks
        }
    }
}

How to add a file from memory#

Java
byte[] data = "hello\n".getBytes(java.nio.charset.StandardCharsets.UTF_8);
TarArchiveEntry entry = new TarArchiveEntry("notes/hello.txt");
entry.setSize(data.length);   // the size must be set before writing data
entry.setMode(0644);
entry.setModTime(System.currentTimeMillis());
tar.putArchiveEntry(entry);
tar.write(data);
tar.closeArchiveEntry();

How to read a .tar.xz or .tar.zst#

Wrap the file stream in the matching compressor stream (needs the xz or zstd-jni dependency):

Java
import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
import org.apache.commons.compress.compressors.zstandard.ZstdCompressorInputStream;

InputStream in = new BufferedInputStream(Files.newInputStream(Paths.get("archive.tar.xz")));
TarArchiveInputStream tar = new TarArchiveInputStream(new XZCompressorInputStream(in));
// for .tar.zst: new TarArchiveInputStream(new ZstdCompressorInputStream(in))

When you don’t know the compression, new CompressorStreamFactory().createCompressorInputStream(in) detects it from the first bytes (the stream must support mark, so pass a BufferedInputStream).

How to read one entry with random access#

TarFile reads all headers once and then opens any entry directly. It works on uncompressed .tar only:

Java
import org.apache.commons.compress.archivers.tar.TarFile;

try (TarFile tarFile = new TarFile(Paths.get("archive.tar"))) {
    for (TarArchiveEntry e : tarFile.getEntries()) {
        if (e.getName().equals("docs/readme.txt")) {
            try (InputStream in = tarFile.getInputStream(e)) {
                System.out.println(new String(in.readAllBytes(), "UTF-8"));
            }
        }
    }
}

Tips#