W3docs

Java Classpath

Set the classpath when compiling and running Java programs so the JVM can find your classes and dependencies.

The compiler knows which packages to look in because of the classpath — the set of locations where Java looks for class files. When you run java MyApp and get a ClassNotFoundException, the cause is almost always classpath: the JVM couldn't find a .class file where it expected to. Understanding how the classpath is built turns "it just doesn't run" into a precise, fixable problem.

What the classpath is

The classpath is an ordered list of locations the JVM searches for .class files. Each location is one of:

  • A directory — taken as the root of a package tree. The JVM looks for com/example/Foo.class underneath it.
  • A JAR file — searched as if its internal directory tree were a directory.
  • A wildcard like lib/* — matches every .jar in lib/ (not recursively, and not loose .class files).

When you reference com.example.Foo, the JVM walks the classpath in order and uses the first match. If two locations contain the same class, the one earlier in the classpath wins — a common cause of "I updated the JAR but the old code is still running."

Setting the classpath

There are three ways to tell the JVM what's on the classpath, in order of preference:

# 1. -cp / -classpath flag (clearest, scoped to the one command):
java -cp out:lib/mylib.jar com.example.App

# 2. The CLASSPATH environment variable (set once, used by every invocation):
export CLASSPATH=out:lib/mylib.jar
java com.example.App

# 3. JAR manifest Class-Path entry (for executable JARs):
java -jar app.jar

-cp overrides CLASSPATH. Setting the environment variable globally is a frequent source of bugs — a stale CLASSPATH from a long-forgotten project causes mysterious behavior. Prefer -cp for every command.

On Windows, the separator is ;. On macOS and Linux, it's :. Quote the value if it contains spaces.

The default classpath

If you don't set one, the JVM uses the current directory (.) as the classpath. That's why javac Hello.java && java Hello works out of the box for default-package files — Hello.class is right there.

The moment you put your code into a package, you need to either run from the right place or pass -cp explicitly:

# Source: src/com/example/App.java with `package com.example;`
javac -d out src/com/example/App.java
java -cp out com.example.App      # must use the fully-qualified name

A common mistake is java -cp out com/example/App. The argument to java is a class name, not a path — use dots, not slashes.

The classpath at compile time

javac has its own classpath, distinct from the one at runtime:

javac -cp lib/dependency.jar -d out $(find src -name "*.java")

-cp here lists locations where javac looks for types your sources reference. Anything those sources import has to either be in lib/dependency.jar or on the implicit classpath. The compiler's -d flag picks where the output .class files land — typically a parallel out/ tree.

For most builds you don't run javac and java by hand. Build tools — Maven, Gradle — assemble the classpath from declared dependencies. The point of understanding it manually is so you can debug what they did when something goes wrong.

JAR files on the classpath

A JAR is a ZIP file with class files and metadata. Put one on the classpath and the JVM treats its contents as another package tree:

java -cp app.jar:lib/json.jar:lib/db.jar com.example.Main

A few practical notes:

  • Wildcards expand to JARs only: -cp lib/* matches every .jar in lib/, not subdirectories or loose .class files.
  • Wildcards are not shell globs. They're handled by the JVM itself. Most shells would expand lib/* first; the JVM expects the literal string lib/*. Quote it to prevent shell expansion: -cp "lib/*".
  • Order matters for duplicates. The first JAR providing a class wins.

Executable JARs

If you set a Main-Class in a JAR's META-INF/MANIFEST.MF, you can run it with just -jar:

java -jar app.jar

Two catches with -jar:

  • -cp is ignored when -jar is used. The only way to add dependencies to an executable JAR's classpath is via the manifest's Class-Path: attribute, listing other JARs.
  • The JAR's Main-Class is mandatory. Without it, -jar refuses to run.

This is why fat JARs — a single JAR containing every dependency, built with the Maven Shade plugin or Gradle's Shadow plugin — became standard. They sidestep the whole executable-JAR classpath dance.

The module path (a brief detour)

Since Java 9 the JVM also has a module path (-p or --module-path), parallel to the classpath. Modules are a tighter, declaration-based packaging unit layered on top of packages. Most application code still runs on the classpath; modules are most visible at the JDK level. You can ignore them while learning the basics and come back to them when a framework asks you to.

A worked example

This program shows the classpath from the inside — what the JVM actually loaded and from where. It uses only java.lang so it runs everywhere.

java— editable, runs on the server

java.class.path reports the application classpath; String's class loader prints as null because core JDK classes come from the bootstrap loader, not from any user-visible classpath entry. The class loader hierarchy is the engine that makes the classpath work — its details are a topic for an advanced chapter.

What's next

That wraps up packages and imports. You now have all the pieces — naming, importing, declaring, finding, and the standard library that sits on the other end of every import line. Up next is one of Java's defining design choices: checked exceptions and the try/catch/finally machinery used to deal with errors. Continue to Java exceptions.

Practice

Practice

A Java program runs fine with `java -cp out:lib/dep.jar com.example.App`, but fails with `java -jar app.jar` even though `app.jar` has `Main-Class` in its manifest and bundles the same `com.example.App` class. Why?