This is Part 2 of my attempt to revisit the Java things many of us learned to use long before we bothered to understand them.
JAR files are a perfect example. I've added dependencies and watched .jar files appear. I've packaged applications into them, run java -jar, and occasionally blamed a JAR when some class mysteriously disappeared at runtime.
For something that shows up in practically every Java learning project, I'd spent surprisingly little time asking what the file itself actually was.
So I opened the case file. It turns out a JAR is considerably less mysterious than the layers of tooling around it can make it seem.
So what exactly is a JAR?
JAR stands for Java Archive, and the first useful fact is that it isn't some opaque Java-specific binary format.
A JAR is an archive based on the ZIP format. It bundles files together into one convenient package. Those files commonly include compiled Java classes, resources, and metadata.
A tiny application JAR might look like this:
my-app.jar
├── META-INF/
│ └── MANIFEST.MF
└── com/
└── example/
├── Main.class
└── Calculator.class
Those .class files are the bytecode produced when Java source files are compiled. The directory structure isn't arbitrary either.
If Main.java begins with:
package com.example;
then its compiled class belongs under the corresponding com/example/ path inside the JAR.
The archive can also contain non-class resources such as configuration files, images, or other data an application or library needs.
Let's open the evidence bag
You don't have to trust your build tool's description of what's inside a JAR. The JDK gives you the jar utility, and you can inspect an archive directly:
jar tf my-app.jar
Here, t tells jar to list the archive's table of contents, while f says the archive name is being supplied. Modern long-form syntax can express the same operation with jar --list --file my-app.jar.
You might see:
META-INF/
META-INF/MANIFEST.MF
com/example/Main.class
com/example/Calculator.class
Because JARs are ZIP-based, ordinary archive tools can inspect them too. The JDK's jar command is still useful because it understands operations and metadata specific to JARs.
This is one of those debugging habits that seems obvious only after you've needed it. If Java claims a class isn't available, checking whether that .class file actually exists in the archive is useful evidence.
That mysterious META-INF folder
META-INF is where a JAR can keep metadata about the archive. For this investigation, the most interesting file is usually:
META-INF/MANIFEST.MF
A minimal manifest for an executable application might contain:
Manifest-Version: 1.0
Main-Class: com.example.Main
Main-Class tells the Java launcher which class is the application's entry point. That class needs an appropriate main method, such as:
public static void main(String[] args)
The manifest can contain other attributes, but we don't need to interrogate all of them today. The important clue is that the JAR can carry information about how it should be used.
So how does java -jar actually work?
Suppose I run:
java -jar my-app.jar
Java isn't opening the archive and guessing which of the 200 classes looks most important.
For an executable JAR, the manifest's Main-Class attribute identifies the class the launcher should load as the starting point. In our example, that's com.example.Main.
This also explains why not every JAR is executable.
A library JAR might contain hundreds of compiled classes and resources without having an application entry point. Its job is to provide code to another application, not to be launched directly.
That's what you're often getting when Maven or Gradle resolves a Java dependency: a library packaged as a JAR that can be made available to your application through the appropriate class path or module path setup.
Same file format, different job.
Building one without hiding behind Maven
Modern build tools make packaging convenient, but doing it manually once makes the mechanics much clearer.
Imagine this source file:
package com.example;
public class Main {
public static void main(String[] args) {
System.out.println("Hello from the JAR");
}
}
First, compile it:
javac -d out src/com/example/Main.java
The -d out option tells javac where to place the compiled output. You'll end up with out/com/example/Main.class.
Now create a basic JAR:
jar --create --file my-app.jar -C out .
The -C out . portion tells jar to change to the out directory and include its contents, preserving the package structure.
If I want an executable JAR, I can specify the entry point while creating it:
jar --create --file my-app.jar --main-class com.example.Main -C out .
The jar tool records that main class in the manifest. I can then run:
java -jar my-app.jar
No Maven required for this little experiment. More importantly, none of the packaging feels particularly magical anymore.
Why knowing this actually helps
Most of us won't manually build production JARs every morning. Maven, Gradle, IDEs, and frameworks exist for good reasons.
Understanding the archive still gives you another debugging surface.
If you're chasing a ClassNotFoundException, you can inspect whether the expected class was packaged. If a resource can't be found, you can check where it ended up. If java -jar won't start the expected application, the manifest becomes an obvious suspect.
Dependency problems also become slightly less abstract once you remember that many of those dependencies are ultimately archives containing real .class files and resources.
Instead of assuming the build system performed mysterious Java rituals incorrectly, you can inspect the artifact it actually produced.
Case closed
JARs feel complicated largely because modern Java development lets us interact with them indirectly.
Strip away the tooling and the basic idea is refreshingly ordinary: a JAR is a ZIP-based Java Archive containing classes, resources, and metadata. Package paths preserve the structure of Java packages, META-INF holds metadata, and an executable JAR can use Main-Class to tell java -jar where execution begins.
The next time packaging gets weird, I'll be slightly less inclined to stare accusingly at pom.xml and slightly more inclined to run jar tf.
What's another piece of Java tooling you use constantly but have never actually stopped to investigate?
Top comments (0)