Apache Commons Compress
| Language | Java (8 and later) |
|---|---|
| License | Apache 2.0 |
| Repository | github.com/apache/commons-compress |
| Documentation | TAR package docs, javadoc |
| Depends on | commons-io, commons-lang3; optional org.tukaani:xz for xz, com.github.luben:zstd-jni for zstd |
| Read / write | yes / 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:
TarArchiveInputStream: reads entries in order from anyInputStream, including a decompressing one.TarArchiveOutputStream: writes entries; you choose how long names and big numbers are stored.TarFile: reads the headers of an uncompressed tar from a file or channel and gives random access to any entry.
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:
<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#
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#
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#
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):
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:
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#
getNextTarEntry()is deprecated; usegetNextEntry().entry.getExtraPaxHeaders()returns pax keys that aren’t mapped to fields, such asSCHILY.xattr.*.TarArchiveEntry.isSymbolicLink()andgetLinkName()give link targets; check them before creating links when extracting untrusted archives.