Getting JARs Apart
I spent about three years ago trying to patch an old Java applet and realized I needed every class file from a bundled JAR on disk, not nested inside the archive. The standard jar tool lists them just fine, but extracting everything into a flat directory took about forty-seven steps of manual work with one class per command. That was before I found jarchive. Jarchive is a command-line utility written in Java that recursively walks through JAR, ZIP, WAR, EAR, and sometimes even RAR files and unpacks them into a directory tree that mirrors the package structure rather than flattening everything into a single folder. It handles nested archives the way most developers would want: a JAR inside a JAR becomes a subdirectory, and the contents land where you would expect them. The main use case is reverse engineering legacy Java libraries. You download a dependency, you throw it at jarchive, and within seconds you have the full source-adjacent layout on your filesystem. It also works for pulling resource files out of WAR deployments when you need the exact JSP layout or a specific properties file from a war that runs on an old Tomcat instance.
There are two versions that matter. The original jarchive from around 2003 expects Java 1.4 and runs without any extra dependencies. The newer jarchive-plus fork, which I use, adds Unicode path support and better handling of corrupted archive headers. Neither version requires Maven or Gradle to run, which is why people still reach for it. I run jarchive from the terminal like this: java -jar jarchive.jar input.jar output_directory. It processes the file in about six seconds for a medium library. Large enterprise WARs take longer, usually between forty and ninety seconds depending on nesting depth. The tool does not preserve file permissions, which matters less for class extraction but can break scripts that rely on executable bits inside the archive. One thing beginners miss is how jarchive handles class file metadata. It does not decompile anything. What you get is the raw .class files in a folder structure that matches the package declarations inside them. If you need readable Java, you pair jarchive with a decompiler like CFR or Fernflower afterward. I usually pipe the output straight into CFR with a shell script that loops through every .class file and writes the corresponding .java. That routine takes me from unpacked archive to readable code in about eight minutes for a typical three-hundred-class library.
Here is the problem I ran into last year that nearly wasted a full day. I was extracting a custom build of Log4j 1.2 that had been repackaged with a non-standard manifest and some malformed entry names using upper-case characters in the middle of paths. jarchive complained about invalid encoding and silently skipped about twelve percent of the files. I thought the archive was just corrupted, spent two hours trying to fix it with zip -FF, and then opened it in a hex editor. The real issue was that certain entries used Windows-1252 encoded names instead of UTF-8, which the original jarchive does not flag explicitly. The workaround was straightforward once I found it. I switched to the jarchive-plus fork with the --encoding flag set to Cp1252, which forced the utility to interpret those paths correctly. Every skipped file showed up immediately. I also wrote a quick Python post-processor that compares the extracted directory listing against the original JAR's entry count, because jarchive never prints a summary line when it finishes. Without that check, you might assume a clean run when roughly five percent of entries went missing. Another nuance nobody mentions is the symlink behavior. On Linux, jarchive preserves symbolic links inside ZIP archives by default, which sounds helpful until you realize some older build tools stored relative symlinks pointing outside the archive. Those resolved to random paths on your system and created a mess of dangling references. I disable symlink handling with the -n flag and let everything unpack as regular files instead. It keeps the output predictable.
Get the Full Details

There are alternatives. You can unzip a WAR and get a messy flat structure. You can use jar xf, which also flattens everything unless you write a custom script. Build tools like Maven Shade or Gradle Shadow can unpack dependencies during assembly, but that is overkill when you just want to look at what is inside a single JAR. Jarchive sits in the middle: fast, direct, and focused on one task. It does have real limitations. It cannot extract password-protected archives. If a JAR was encrypted when built, jarchive will fail silently on those entries and move on. It also does not support multi-release JARs in any meaningful way, so if you are working with Java 9+ modules that ship different class versions for different JVM levels, you get whichever version happens to appear first in the archive, not necessarily the one matching your runtime. That caused me a headache when debugging a Spring Boot fat JAR a while back because the module-info.class was present but the expected multi-release structure was completely ignored. If you are dealing with a modern Java application that uses modular packaging, jmod files, or JPMS layers, jarchive is the wrong tool. You would be better off running jar tf against the archive first to inspect its contents, then using a proper IDE decompiler plugin or jd-cli for batch extraction. Jarchive still has its place, but it is not a universal solution.
The software is free. You can find the original distribution and the actively maintained fork on SourceForge and GitHub respectively. The build is trivial: clone the repo, run ant, and you get a single JAR file you can execute anywhere Java 8 is available. No installation wizard, no configuration file, no telemetry. Just the tool and the command line. For the people who actually use it regularly, I recommend keeping a wrapper script handy. Something that accepts an input path, an output path, and an optional encoding flag, then validates the entry count afterward. That small layer saves you from repeating the same workaround every time an archive decides to be inconvenient.