skip to content

How do you make a JAR runnable with `java -jar` and how does the Class-Path header relate to dependencies?

level: middleimportance: must knowfreq 65%

answer

  1. Main-Class header => java -jar
  2. -jar ignores command-line -cp
  3. addClasspath + classpathPrefix
  4. Class-Path is relative to the JAR
  5. fat JAR = shade/assembly

basics

~10 s

Set <mainClass> under <archive><manifest> so Maven writes Main-Class to the manifest; then java -jar works. Add <addClasspath>true</addClasspath> to write a Class-Path header pointing at dependency JARs.

solid answer

~40 s

To run a JAR directly you need a Main-Class manifest header. With maven-jar-plugin you set <archive><manifest><mainClass>com.example.App</mainClass></manifest></archive>; Maven then writes Main-Class and java -jar app.jar runs it. But a plain jar does NOT bundle dependencies — when Main-Class loads code from another library, the JVM can't find it unless those JARs are on the classpath. The Class-Path manifest header (enabled with <addClasspath>true</addClasspath>, often with <classpathPrefix>lib/</classpathPrefix>) lists relative paths to dependency JARs so java -jar resolves them. The catch: those JARs must physically exist at those relative locations. For a self-contained executable JAR people instead use maven-shade-plugin (fat/uber JAR) or maven-assembly-plugin, which inline all dependencies.

code

xml · 7 lines
xml
<archive>
  <manifest>
    <mainClass>com.example.App</mainClass>
    <addClasspath>true</addClasspath>
    <classpathPrefix>lib/</classpathPrefix>
  </manifest>
</archive>

go deeper

for a junior

Know that Main-Class makes java -jar work.

for a middle

Know addClasspath/classpathPrefix and that Class-Path is relative file paths the JARs must actually be at.

for a senior

Judge between Class-Path+lib/ vs shade/assembly uber JARs and the package-relocation trade-offs.

for a principal

Define org-wide packaging strategy (shade vs Spring Boot repackage vs jlink), including reproducibility and classpath-vs-modulepath implications.

## Making a JAR executable `java -jar app.jar` works only if the manifest has a **`Main-Class`** header naming a class with a `public static void main(String[])` method. The JVM ignores any `-cp`/`-classpath` you pass on the command line when you use `-jar` — the classpath comes *entirely* from the manifest. With Maven: ```xml <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <archive> <manifest> <mainClass>com.example.App</mainClass> <addClasspath>true</addClasspath> <classpathPrefix>lib/</classpathPrefix> </manifest> </archive> </configuration> </plugin> ``` This writes: ``` Main-Class: com.example.App Class-Path: lib/guava-33.0.0.jar lib/commons-lang3-3.14.0.jar ``` ## The Class-Path header gotcha `Class-Path` entries are **relative URLs resolved against the JAR's own location**. They are *not* absolute and *not* the Maven repository. So if your manifest says `lib/guava-33.0.0.jar`, a `lib/` folder with those JARs must sit next to `app.jar` at runtime. Typically you also run `maven-dependency-plugin`'s `copy-dependencies` goal to populate `target/lib/`. Note: `Class-Path` only contributes to the **application classpath**, not the module path, and it cannot contain Maven coordinates — only file paths. ## The self-contained alternative: uber/fat JARs Because shipping a `lib/` folder is fragile, most teams build a single self-contained JAR: - **maven-shade-plugin** — unpacks all dependency classes into one JAR, can relocate packages to avoid conflicts, and writes `Main-Class` via its `ManifestResourceTransformer`. - **maven-assembly-plugin** with the `jar-with-dependencies` descriptor — simpler, but doesn't relocate or merge service files as cleanly. - Spring Boot uses its own **spring-boot-maven-plugin** repackage (nested JARs, a special `Main-Class` launcher plus `Start-Class`). ## Decision summary | Goal | Use | |------|-----| | Plain library JAR | maven-jar-plugin only | | Runnable + external lib/ folder | jar-plugin + addClasspath + dependency-plugin | | Single runnable artifact | maven-shade-plugin (or assembly) |

  • Why does `java -cp other.jar -jar app.jar` not pick up other.jar?
    With -jar the JVM derives the entire classpath from the manifest's Class-Path header and ignores -cp on the command line.
  • Your runnable JAR throws NoClassDefFoundError for a dependency. What's the usual cause?
    The plain jar doesn't bundle dependencies and either Class-Path is missing or the referenced lib/ JARs aren't physically present next to the JAR; build a shaded/uber JAR or populate lib/.
  • Why would you prefer maven-shade-plugin over the Class-Path header for production?
    A shaded uber JAR is self-contained — one file, no fragile relative lib/ folder — and can relocate conflicting packages.

saying these in an interview costs you the question

  • Claiming a plain maven-jar-plugin JAR bundles its dependencies
  • Saying -cp on the command line overrides the manifest when using -jar
  • Thinking Class-Path can hold Maven coordinates or absolute repo paths instead of relative file paths

context