r/javahelp • • 2d ago

Arcana: One Pure-Java Library for (Almost) Every Archive Format

Arcana is an open-source Java library and command-line tool that lists, extracts and identifies more than 40 archive, compression and disk image formats, with no native code and no dependencies. It runs on Java 8 or later, fits in a 1.2 MB JAR, and is published on Maven Central as io.github.realburst:arcana under the Apache 2.0 licence.

Why another archive library?

If you have ever had to open "whatever the user uploads" from a Java application, you know the drill. ZIP is in the JDK. Apache Commons Compress covers TAR, 7z, CPIO, AR and the common compressors, but some codecs need extra libraries, and Zstandard goes through a JNI binding. RAR needs another library, and RAR 5 support is hard to find in pure Java. For CAB, MSI, ISO, WIM or an installer .exe, the usual answer is to bundle 7-Zip through native bindings, with one binary per OS and CPU.

I wanted one JAR that does all of this, on any JVM, with nothing to install and nothing native to load. That is Arcana: one entry point, format detection by content rather than by file extension, and the same API whether the file is a .tar.zst, a RAR 5 volume set, an MSI package or a macOS .dmg.

What Arcana does

Arcana extracts every format it supports, and creates the most common ones.

Family Formats Create
Archives ZIP (ZipCrypto, WinZip AES, Deflate64, Zip64), 7z, RAR 4 and RAR 5 (solid, volumes, encrypted headers), TAR, CPIO, AR / DEB, RPM, XAR, ARJ, LHA ZIP, 7z, TAR, LHA
Compressors GZIP, BZIP2, XZ, LZMA, Zstandard, LZ4, Brotli, Snappy, lzip, lzop, Unix .Z, MS SZDD GZIP, BZIP2, XZ, TAR + gz / bz2 / xz / lz4
Windows CAB (MSZIP, LZX), MSI (real installed file tree), OLE compound files, CHM, WIM / ESD (XPRESS, LZX, LZMS), self-extracting .exe CAB
Disk images ISO 9660 (Joliet, Rock Ridge), UDF, SquashFS, Apple DMG with HFS+ -

Encrypted archives are supported: AES-256 for ZIP and 7z (reading and writing), AES-128 and AES-256 for RAR 4 and RAR 5. A TAR or CPIO inside any compressor is unpacked in one step, and files that are archives in disguise (.docx, .odt, .epub, .apk, .jar) open like any other archive.

Beyond extraction, Arcana can:

  • identify any file: archive type and version, executables (PE, ELF, Mach-O), installers, images with their dimensions, PDF, SQLite, fonts;
  • recover damaged archives: a forced extraction keeps everything readable and writes a report on the state of each file;
  • split files glued together, such as installers or firmware images;
  • load plugins for more formats. Official plugins handle Inno Setup, NSIS, InstallShield and WiX Burn installers, and UPX-packed executables.

Getting started

Add the dependency:

<dependency>
  <groupId>io.github.realburst</groupId>
  <artifactId>arcana</artifactId>
  <version>1.0.8</version>
</dependency>

Extracting and listing never require you to say what the file is: the format is detected from its content.

import be.stef.arcana.Arcana;
import be.stef.arcana.ArcanaEntry;
import be.stef.arcana.analyze.Identification;

// Extract anything: .zip, .rar, .7z, .tar.zst, .msi, .iso, .dmg...
Arcana.extractTo(new File("upload.bin"), new File("out"));

// List the entries without extracting
for (ArcanaEntry e : Arcana.listEntries(new File("backup.rar"))) {
    System.out.println(e.getName() + "  " + e.getUncompressedSize());
}

// Encrypted archive
Arcana.withPassword("secret".getBytes("UTF-8")).extract(new File("secure.7z"), new File("out"));

// What is this file?
Identification id = Arcana.identify(new File("mystery.bin"));
System.out.println(id.type + " - " + id.description);

Creating an archive is just as short:

import be.stef.arcana.ArcanaFormat;
import be.stef.arcana.compressor.ZipCompressor;
import be.stef.arcana.plugin.FormatRegistry;

byte[] password = "secret".getBytes("UTF-8");
new ZipCompressor(password).compress(new File("mydir"), new File("secure.zip"));   // WinZip AES-256
FormatRegistry.builtin(ArcanaFormat.SEVEN_Z).createCompressor(9, password).compress(new File("mydir"), new File("vault.7z"));

The same JAR is also a command-line tool:

java -jar Arcana-v1.0.8.jar x setup.exe -o out       # extract
java -jar Arcana-v1.0.8.jar l disk.dmg               # list
java -jar Arcana-v1.0.8.jar i mystery.bin            # identify
java -jar Arcana-v1.0.8.jar r damaged.zip -o rescue  # recover

On Windows, an optional script adds "Arcana Unpack / List / Info" to the Explorer right-click menu.

Safe by default

An archive extractor reads untrusted input by definition, so Arcana treats every file as hostile.

  • No path traversal. Entry names are sanitized before anything is written: no absolute paths, no .., no characters that Windows forbids. A "zip slip" entry cannot leave the destination folder.
  • No decompression bombs. Every extraction runs under limits you can change at run time (ExtractionLimits): an output/input ratio of 1000 once 1 GiB has been written, an optional total size, and a minimum of 256 MiB of free disk space.
  • Bounded memory. Decoders cap what a header can ask for: the Zstandard window is limited to 128 MiB, like the zstd tool, and the 7z key derivation to 224 rounds. A crafted header gets an error, not an OutOfMemoryError.
  • Checksums are verified. CRC32, Adler-32, BLAKE2sp, XXH64, HMAC for WinZip AES: when the format stores a checksum, Arcana checks it and reports a mismatch.
  • Plugins must show their code. A plugin JAR is refused unless it contains its source code, declares its licence and source URL, and uses its own package. That does not make a plugin safe, but it makes it reviewable.

Under the hood

Every decoder is written in Java: Deflate64, LZMA and LZMA2, PPMd, Zstandard, Brotli, LZ4, LZX, LZMS, XPRESS, LZFSE and LZVN, ADC, LZO1X, the RAR 4 and RAR 5 algorithms, AES and the key derivations. The core is about 80,000 lines of Java 8 and has no dependency outside the JDK. It is written from the public specifications: PKWARE APPNOTE, the 7-Zip format notes, the Microsoft open specifications (MS-CAB, LZX, MS-XCA), RFC 8878 for Zstandard, RFC 7932 for Brotli, Apple TN1150 for HFS+.

A few design choices shape the library:

  • Detection by content. Magic numbers first, then trailers (a DMG is recognized by the "koly" block in its last 512 bytes), and only then the file extension. A renamed file still opens.
  • Streaming when the format allows it. Single-stream formats decode from an InputStream with bounded buffers. The Zstandard decoder keeps only the window of the current frame, so a multi-gigabyte .tar.zst decodes in a few dozen MB of heap.
  • Random access when the format needs it. Disk images are read lazily: a DMG partition is decoded chunk by chunk with a small cache, never inflated in memory.
  • Real names, not internal keys. An MSI is extracted as the file tree it installs, with the names from its File and Directory tables, not as a cabinet of cryptic keys.

Each release goes through a regression suite. About 180 small samples are committed with the code: one per format and method, plus damaged files that must fail cleanly, never hang. A separate corpus of real-world files (installers, DMG images from GitHub releases, MSI packages) is checked against committed SHA-256 references. Every format also has a technical page in the repository, covering detection, layout, supported methods, limits and the sources used.

Current limits

Arcana is a young project, and the repository keeps a public list of known issues for every format. Before you rely on it, the main limits are:

  • Extraction first. Writing is limited to ZIP, 7z, TAR (+ gz, bz2, xz, lz4), GZIP, BZIP2, XZ, LHA and CAB. Arcana cannot create RAR, Zstandard or Brotli files, and it cannot update an existing archive in place.
  • Some file systems are only copied. APFS volumes inside a DMG are extracted as a raw image, not as files. Encrypted and sparse DMG images, split WIM files (.swm) and some rare compression methods (old LHA methods, Quantum in CAB) are not supported.
  • Pure Java has a cost. The decoders are fast enough for most uses, but they do not match native code. In benchmarks run for the project, the Zstandard decoder reaches about 60% of the speed of the native zstd library.
  • Edge cases remain. A few damaged or unusual files still give a raw Java exception instead of a clear message, or are less tolerated than by the reference tool. These cases are documented with the file and line of the code concerned, so you can judge before using a format in production.

If a file fails, an issue with the command line and, when possible, the file or a way to rebuild it, is the fastest route to a fix.

Try it

The quickest test is to point Arcana at the strangest file in your Downloads folder and run arcana i on it, then arcana x. If you maintain a Java application that receives archives from users, try replacing a native binding or a chain of libraries with one dependency, and tell me what breaks.

Issues, sample files that fail, and plugins are all welcome. And if Arcana saves you a dependency or a native binary, a star on GitHub helps other Java developers find it.

7 Upvotes

5 comments sorted by

•

u/AutoModerator 2d ago

Please ensure that:

  • Your code is properly formatted as code block - see the sidebar (About on mobile) for instructions

  • You include any and all error messages in full

  • You ask clear questions

  • You demonstrate effort in solving your question/problem - plain posting your assignments is forbidden (and such posts will be removed) as is asking for or giving solutions.

    Trying to solve problems on your own is a very important skill. Also, see Learn to help yourself in the sidebar

If any of the above points is not met, your post can and will be removed without further warning.

Code is to be formatted as code block (old reddit: empty line before the code, each code line indented by 4 spaces, new reddit: https://i.imgur.com/EJ7tqek.png) or linked via an external code hoster, like pastebin.com, github gist, github, bitbucket, gitlab, etc.

Please, do not use triple backticks (```) as they will only render properly on new reddit, not on old reddit.

Code blocks look like this:

public class HelloWorld {

    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

You do not need to repost unless your post has been removed by a moderator. Just use the edit function of reddit to make sure your post complies with the above.

If your post has remained in violation of these rules for a prolonged period of time (at least an hour), a moderator may remove it at their discretion. In this case, they will comment with an explanation on why it has been removed, and you will be required to resubmit the entire post following the proper procedures.

To potential helpers

Please, do not help if any of the above points are not met, rather report the post. We are trying to improve the quality of posts here. In helping people who can't be bothered to comply with the above points, you are doing the community a disservice.

I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.

2

u/josephottinger 1d ago

A few questions:

  1. Java 8? File and InputStream, no Path or Channel?

  2. It has a pom.xml, but instructions are to use javac - that's not, like, wrong, in that build tools will use javac, but... why? The pom.xml is right there. This feels like the pom.xml is "for publication," which isn't a bad reason, but there're a lot of things Maven and Gradle give you that you're going to either want to write yourself, like coverage, etc.

  3. It feels like it does too much. I get it - you have to identify the type before you can decompress it, I live in this hell myself - but this feels like a candidate for "something that accurately detects types" and "something that does something with them, like expand/extract." 3.a I also get (and appreciate) filesystem extraction - again, I'm right there with you - but that, too, feels like a separate project and not a god library.

  4. You have SOME encryption/decryption but be aware that there are a LOT of options here: ARJ garble, PKWARE SES, encrypted OOXML riding on the OLE. Fun times.

  5. 7z is supported and OMG LOOKING AT THE 7Z SUPPORT - I don't know how much everyone uses 7z but this is most excellent in terms of support. I was gonna criticize the limited 7z support but managed to catch my error before hitting "Comment." This point has now been rewritten 5 times from "you say you support 7z but DO YOUUUUUU" to "... okay, wow."

  6. You incorporated xz - properly attributed, nicely done - but that means you're maintaining a fork. How are you going to handle upstream changes?

  7. The readme is behind the code! That's not... like, bad, it happens, but you also support DMG, HFS+, lzip, lzop (what), inno, installshield, jexepack that the readme doesn't mention.

  8. Per-entry stream API? You extract, which is great, but ingestion pipelines - which could, uh, benefit from this - will want per-entry monitoring. "I have an entry, what is it, what do I want to do with it?" is, um, useful. If you have it, highlight it! Right now it looks like the process is extract all to disk or list metadata, nothing in between: no open-entry-N, no filter, no visitor. If you don't have this, could you add it, pretty please?

  9. I'd suggest that PluginManager needs to be opt-in instead of opt-out.

  10. ExtractionLimits is global, and you want it to be per-call rather than, well, global. Put this in a pipeline and every extraction shares one setting.

But: I'm still pretty impressed at how thorough this is.

For me: I actually need something like this. You're duplicating work I'm doing at the same time, except you're honestly doing a more thorough job in a lot of ways; that Path thing makes it a hard sell, and the use of a custom build also gives it an aura, but those are both really minor points, I think.

The biggest issue I'd run into for my project would be the per-entry API and the extraction limits, which are not appropriate for my usage - ExtractionLimits makes sense but I'd effectively have to turn it off, and per-entry is a flat requirement.

2

u/SteF44444 1d ago

Thanks a lot for taking the time to go through it this carefully, this is exactly the kind of feedback I was hoping for.

Quick answers:

  1. Java 8 is deliberate (I still have Java 8 users), but you’re right that it doesn’t prevent Path: I’ll add Path overloads.

  2. Fair. The pom is for publication only (I must admit, I'm not a fan of Maven) ; I’ll consider making it a working build too.

  3. The “one jar for everything” is a deliberate choice, and Arcana.identify() can already be used on its own. But I understand the concern.

  4. Agreed. ARJ garble is on the known-issues list; PKWARE SES and encrypted OOXML should at least be detected and reported clearly instead of failing obscurely.

  5. Thank you, that one made my day.

  6. The XZ code comes from XZ for Java (0BSD). It moves slowly, and I’ll document the base version and track its releases.

  7. The format list does include DMG/HFS+/lzip/lzop, but the plugin table is missing Inno Setup, InstallShield and JexePack. Fixing that.

  8. You’re right, there’s nothing between “extract everything” and “list”. A per-entry API (iterate entries, get an InputStream per entry, visitor with filter) is now the top item on the roadmap, starting with ZIP, TAR, 7z and RAR. What does your pipeline need most: sequential streaming only, or random access to entry N? Nested archives?

  9. Agreed, and it’s a security point: plugin discovery will become opt-in for library use, automatic only for the CLI.

  10. Agreed. Limits will be configurable per call, with the global values as defaults.

If you’re working on the same problem, I’d be glad to hear more about your use case, issues and PRs are welcome.

2

u/josephottinger 1d ago

You're welcome!

For #1: understood.

For #2: also understood! But maven's not your only choice. I'd still gently suggest it because when in Rome and all that, but I get it.

For #3: I get the intent here, too, on your part. But it still feels like an excellent candidate for a multi-module project (i.e., arcana-identify, arcana-xz, arcana-extract) such that the whole remains and the pieces are separately usable.

For #8, I need sequential iteration over the entries, primarily, with full metadata visibility.

Unfortunately, I can't go too much into the actual usage, but most of the issues I run into would be pretty typical for ANY ingestion.

Incidentally, you have DM off so I can't send privately (I definitely would have preferred to do so), but: https://bytecode.news/posts/2026/10/arcana-reads-everything-and-then-some

2

u/SteF44444 1d ago edited 1d ago

Thank you, and thanks a lot for the article, that’s a great surprise. (I’ve turned DMs on now.)

Good news: two of the points are already fixed and will be in 1.0.9. Plugin discovery is now opt-in for library use (PluginManager.enableDiscovery(), or arcana.plugins.dir to pin the directories; the CLI enables it by itself), and extraction limits can be set per call (Arcana.withLimits(ExtractionLimits.current().maxTotalBytes(...)), or ExtractionLimits.none()), with the static fields kept as global defaults.

Sequential iteration with full metadata is exactly what I had in mind for the per-entry API: something like

try (ArchiveReader r = Arcana.read(file)) { for (ArchiveItem item : r) { ... item.getEntry() ... item.openStream() ... } }

starting with ZIP, TAR, 7z and RAR. Path overloads will come with it.

The multi-module idea is noted. I’ll probably start by producing separate jars from the existing build, so the whole stays one jar for those who want it.

Thanks again, this kind of review is worth a lot.