Home › Java › Could Not Find or Load Main Class: Java Fix
Beginner 6 min · September 23, 2026

Could Not Find or Load Main Class: Java Fix

Run java with the class name, not Foo.class, from the classpath root with -cp .

N
Naren Founder & Principal Engineer

20+ years shipping production Java in banking & fintech. Lessons pulled from things that broke in production.

Follow
✓ Production
production tested
September 24, 2026
last updated
1,942
articles · all by Naren
Before you start⏱ 10 min
  • ✓Java installed with javac and java on your PATH
  • ✓A small project with one packaged class to experiment on
  • ✓Basic comfort with cd, ls, and running commands in a terminal
 ● Production Incident 🔎 Debug Guide
⚡Quick Answer
  • You're launching from the wrong directory or naming the file instead of the class: run java Foo from the classpath root, never java Foo.class.
  • A packaged class like com.shop.Main must live at com/shop/Main.class under some root, and you must launch it by its full name from that root.
  • The -cp flag replaces CLASSPATH for one launch, so java -cp .:lib/* com.shop.Main beats any stale environment variable.
  • JAR launches need a Main-Class line in META-INF/MANIFEST.MF; check it with unzip -p and rebuild with jar cfe when it's missing.
✦ Definition~90s read
What is Java Could Not Find Main Class Fix?

Error: Could not find or load main class is the java launcher's lookup failure, thrown before your code ever runs. When you type java com.shop.Main, the launcher searches every classpath root for com/shop/Main.class, loads the bytes, verifies the class, and looks for a valid public static void main.

★
Picture a mailroom that delivers letters by department and name, like Sales/Ana.

If any link in that chain snaps — no root holds the path, the file isn't where the package says it is, or main has the wrong shape — you get this message and the JVM exits without executing a single line of yours.

It's worth separating the two halves of the message. Could not find means the class bytes weren't located on the classpath: wrong directory, wrong -cp, package-folder mismatch, or a misspelled name. Could not load means the bytes were found but rejected: a corrupt class file, a version the runtime can't read, or a main method with the wrong signature.

Both print similarly, but find-problems need classpath fixes while load-problems need build or signature fixes.

The lookup order matters in larger apps. The launcher scans classpath entries left to right and takes the first match, so a stale copy of Main in an early JAR shadows the fresh one you meant to run. That staleness produces bizarre symptoms — old behavior with new code — under this same error family when the stale copy itself is broken.

Ordering your -cp deliberately, roots before libraries, avoids the shadow.

For JAR launches the mechanism shifts: java -jar skips the class name entirely and reads META-INF/MANIFEST.MF for Main-Class, then loads it from inside the archive. A missing manifest entry fails here even with a perfect classpath, because -jar doesn't consult -cp at all. Knowing which launch mode you're in tells you which mechanism to debug — and that's half the battle with this error.

Plain-English First

Picture a mailroom that delivers letters by department and name, like Sales/Ana. You must hand the letter in at the front desk with the full address. If you walk into the Sales room and ask for Ana, the clerk can't help — you're standing in the wrong spot. If you write Sales/Ana.class on the envelope, there's no such person. That's this error: your class exists, but you asked from the wrong directory or with the wrong name, so the JVM's mailroom reports it missing.

You've just compiled cleanly, the .class file is sitting right there, and java still tells you it can't find your main class. It's maddening because the message sounds like your code is missing when the real problem is almost always where you stand, what you typed, or what you told the JVM to search. This error is the launcher's way of saying the lookup failed — not that your program is broken.

The confusion runs deep because five different mistakes produce the identical message. Standing inside the package directory instead of above it. Typing java Foo.class instead of java Foo. A package line that doesn't match the folders. A CLASSPATH variable that points somewhere stale. A JAR whose manifest never named a main class. Each one needs a different fix, so guessing wastes an afternoon.

IDEs hide the whole mechanism. They build the classpath silently, so code that runs with the green arrow dies in a terminal. That's not a code bug — it's a classpath you never had to think about.

This guide walks through all six angles: classpath roots versus the current directory, package-to-folder mapping, naming the class instead of the file, -cp versus CLASSPATH, JAR manifests, and the IDE-to-CLI gap. You'll get a production incident where a bad start script faked a broken build, a debug guide with exact commands, and comparisons that match each symptom to its fix.

The Classpath Root Versus the Current Directory

The JVM doesn't search your current directory for the class file — it searches each classpath root for the package path beneath it. When you run java -cp . com.shop.Main, the dot means this directory is a root, and the launcher looks for ./com/shop/Main.class. If you're standing inside com/shop already, that lookup becomes ./com/shop/com/shop/Main.class, which obviously doesn't exist.

Think of the root as the place where the package tree starts. One project can have several roots: the current directory, an output folder like out/, and every JAR on the classpath. The launcher checks each root in order and loads the first match. That's why java -cp out com.shop.Main works from anywhere — the root travels with the flag instead of depending on your shell's location.

The default classpath when you pass no -cp is just the current directory. That's the trap: developers compile into out/ but launch from the project root, or launch from inside the package folder, and in both cases the default root points at the wrong place. You'll stare at Main.class in ls output while the JVM insists it can't see it.

The fix is boring on purpose. Decide on one root (usually your build output dir), cd there or name it in -cp, and always launch with the fully qualified name. Start scripts should do both explicitly: cd to a fixed directory first, then pass -cp. That removes the shell's location from the equation entirely, which is exactly what you want on a server you'll never log into by hand.

classpath-root-demo.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
# Layout: compiled classes live under out/
# out/com/shop/Main.class
find out -name "*.class"

# WRONG: standing inside the package folder
cd out/com/shop
java Main
# Error: Could not find or load main class Main

# RIGHT: stand on the classpath root, use the full name
cd ../../../
java -cp . com.shop.Main
# Shop service starting...
📊 Production Insight
Deploys break when the start script's working directory drifts from the build layout. Log pwd and the full java command at startup so the next incident starts with the answer instead of a guessing game.
🎯 Key Takeaway
The classpath root is where the package tree starts — launch from it with the full class name, and hard-code both in scripts.

Package Names Must Match the Folder Tree

A package declaration is a promise about folders. Writing package com.shop; means the compiled Main.class must live at com/shop/Main.class relative to some classpath root. The compiler won't force the source file to sit in matching folders, so you can break this promise without any error — javac happily emits Main.class wherever you tell it, and the failure waits for launch day.

The classic breakage is compiling without -d. Running javac src/com/shop/Main.java drops Main.class next to your shell or beside the source, with no com/shop tree around it. The class file is valid, the code is correct, and no root on earth can resolve com.shop.Main from it, because the required path fragments simply aren't there.

javac -d exists to prevent exactly this. The -d flag names the root, and the compiler creates the package folders beneath it automatically. javac -d out src/com/shop/Main.java always yields out/com/shop/Main.class regardless of where your shell stands. Make -d non-negotiable in every build script and Makefile and this whole category of failure disappears.

When you're already broken, confirm with find: find . -name Main.class should show a path ending in com/shop/Main.class. If it shows Main.class sitting bare in some folder, the tree is wrong — recompile with -d and relaunch from the root you gave -d. Don't shuffle .class files by hand; you'll fix one launch and break the next.

src/com/shop/Main.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
// File: src/com/shop/Main.java
// Compile: javac -d out src/com/shop/Main.java
// Result:  out/com/shop/Main.class  (tree built for you)
package com.shop;

public class Main {
    public static void main(String[] args) {
        System.out.println("Shop service starting...");
    }
}
// Launch from the root: java -cp out com.shop.Main
📊 Production Insight
Hand-rolled build scripts that skip -d produce layouts no classpath can resolve. CI should assert the packaged tree with find or jar tf before anything ships.
🎯 Key Takeaway
package com.shop means com/shop/Main.class under a root — compile with javac -d so the tree is always right.

java Foo.class Versus java Foo: Name the Class, Not the File

The java command wants a class name, full stop. java Foo means find class Foo on the classpath and run its main method. java Foo.class means find package Foo, class class — which doesn't exist — and the launcher reports it exactly like a missing class. The file suffix belongs to javac and the filesystem; it must never appear after java.

This bites in two directions. Newcomers type the filename they see in ls, and veterans copy-paste a path from an error log. Both get the same confusing message, and both then chase classpath settings that were never the problem. If your failing command ends in .class or .java, stop touching the classpath — the command itself is wrong.

There's a subtler sibling: the class loads but has no valid main. A missing static, a non-public modifier, or a wrong parameter type all compile cleanly, then fail at launch with a main-method variant of this error. The message says it can't find or load the main class, which reads like a classpath problem but is really a signature problem. Check that main is exactly public static void main(String[] args).

Build the habit of launching from a script or an alias instead of typing the command fresh each time. Scripts don't develop typos under deadline pressure, and a smoke test that runs the script catches both the suffix mistake and the signature mistake before your users do. Your future self at 2 AM will be grateful.

src/Main.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
public class Main {
    // The ONLY signature the launcher accepts:
    public static void main(String[] args) {
        System.out.println("launcher found me");
    }

    // Each of these compiles, yet the launcher rejects them:
    // public void main(String[] args) {}      // missing static
    // public static void main(String args) {} // wrong parameter type
    // static void main(String[] args) {}      // not public
}
📊 Production Insight
Launch commands typed by hand during incidents are where .class suffixes sneak in. A checked-in start script plus a pipeline smoke test removes the human-typing layer where this mistake lives.
🎯 Key Takeaway
java takes class names, never file names — drop the .class suffix and keep main exactly public static void main.

-cp and CLASSPATH: The Flag Wins Every Time

CLASSPATH is a global variable with local consequences. It points somewhere sensible on your laptop and somewhere stale — or nowhere — on the server, in CI, and in containers. Code that launches fine where CLASSPATH happens to be right will fail everywhere else with this error, and nobody will think to blame an environment variable they didn't know existed.

The -cp flag (or -classpath, same thing) replaces CLASSPATH completely for one launch. That's what makes it the fix: java -cp .:lib/* com.shop.Main searches the current directory plus every JAR in lib/, regardless of what the environment claims. When the flagged version works and the bare version doesn't, you've proven the variable was the problem.

Two details trip people up. First, the lib/ wildcard only matches .jar files directly inside lib/, not in subfolders, and it must be quoted (or at least protected) so your shell doesn't expand it before the JVM sees it. An unquoted lib/ in an empty directory can vanish into nothing. Second, separators differ: colon on Linux and macOS, semicolon on Windows. A copied Linux command fails on Windows for that reason alone.

The production rule is simple: never rely on CLASSPATH in anything that ships. Set -cp on every java invocation in scripts, Dockerfiles, and systemd units. If you must keep the variable for interactive use, treat a bare java launch as a convenience and the flagged launch as the truth — and debug against the flagged one.

classpath-flag-demo.shBASH
1
2
3
4
5
6
7
8
9
10
11
# What's the environment claiming?
echo "CLASSPATH=$CLASSPATH"

# Explicit flag beats the environment every time
java -cp .:lib/* com.shop.Main

# Quote the wildcard so the shell passes it to the JVM
java -cp ".:lib/*" com.shop.Main

# See exactly which classes load from where
java -cp .:lib/* -verbose:class com.shop.Main 2>&1 | head -20
📊 Production Insight
Environment-dependent launches are why staging passes and production fails. Bake -cp into Dockerfiles and systemd units so the classpath travels with the artifact instead of living in each box's profile.
🎯 Key Takeaway
-cp replaces CLASSPATH for one launch — always set it explicitly in scripts and quote lib/* wildcards.

JAR Manifests: When java -jar Can't Find Main-Class

java -jar doesn't take a class name at all — it asks the JAR's manifest which class to run. That answer lives in META-INF/MANIFEST.MF as a Main-Class: com.shop.Main line. Build tools sometimes produce JARs without it (plain library packaging, a hand-rolled jar cf, a shade misconfiguration), and then the launcher reports it can't find the main class even though the class bytes sit right inside the archive.

Diagnosing this takes two commands. jar tf shop.jar | grep Main proves the class is present; unzip -p shop.jar META-INF/MANIFEST.MF shows what the manifest claims. When the class is there but the Main-Class line isn't, you've found it — no classpath archaeology needed. This split saves hours because it separates a packaging bug from a lookup bug in seconds.

The fastest repair is jar cfe, whose e flag writes the entry point for you: jar cfe shop.jar com.shop.Main -C out . packages everything under out/ and stamps the manifest. For the long term, declare it in your build — Maven's maven-jar-plugin archive/manifest block or Gradle's manifest { attributes 'Main-Class': ... } — so every artifact leaves the pipeline runnable. Then verify in CI by reading the manifest back, not by assuming the config worked.

One more trap: java -jar ignores -cp and CLASSPATH entirely. Dependencies must ride in the manifest's Class-Path entry or inside the JAR itself as a fat JAR. If your -jar launch suddenly can't see lib/, that's why — the flag you added is being silently ignored by design.

manifest-fix-demo.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
# What's inside the JAR, and what does its manifest claim?
jar tf shop.jar | grep -E "Main|MANIFEST"
unzip -p shop.jar META-INF/MANIFEST.MF

# A runnable JAR's manifest must contain this line:
# Main-Class: com.shop.Main

# Rebuild the JAR with the manifest written for you
jar cfe shop.jar com.shop.Main -C out .

# Now the one-flag launch works
java -jar shop.jar
📊 Production Insight
Pipelines that build library JARs and runnable JARs with the same config ship unrunnable artifacts. Assert the Main-Class line in CI for every artifact that's meant to launch.
🎯 Key Takeaway
java -jar reads Main-Class from the manifest and ignores -cp — inspect with unzip -p and rebuild with jar cfe.

IDE Versus CLI: Why the Green Arrow Works and the Terminal Doesn't

IDEs are wonderful liars about classpaths. IntelliJ, Eclipse, and VS Code assemble a launch classpath from your module settings, dependency lists, and output folders, then run java with a -cp flag so long you'd never type it. Your code works because the tool did the lookup work for you invisibly. The moment you step outside — a terminal, a Docker build, a teammate's machine — that invisible classpath vanishes and this error appears on code that never changed.

The tell is the long command line. Most IDEs can show the exact java invocation they used (IntelliJ prints it atop the run console; Eclipse shows it in debug configs). Copy that -cp value and you'll see output directories and dozens of JARs you'd never have listed by hand. Recreate the essentials of it in your script and the CLI failure usually clears immediately.

Output folders are the other half of the gap. IDEs compile to out/ or target/ and point the runtime there; a hand-rolled javac without -d scatters .class files beside sources. Same sources, different layouts, different results. Align them by building with your real build tool (Maven, Gradle) and launching against its output dir, not by copying .class files around until it works.

Treat the IDE as a development convenience and the script as the contract. If it doesn't launch from the command line with an explicit -cp, it isn't really runnable — it's just IDE-runnable. Make the pipeline, not the green arrow, the definition of working.

⚠ The IDE Is Not the Runtime
If it only runs inside your IDE, you don't have a working program — you have a working IDE configuration. Prove every project with a terminal launch before you commit.
📊 Production Insight
Releases built from IDE exports instead of the pipeline carry invisible classpath assumptions. Ship only pipeline-built artifacts launched by checked-in scripts, and this gap closes permanently.
🎯 Key Takeaway
IDEs build a hidden -cp for you — copy it, recreate it in a script, and make CLI launch the definition of working.
● Production incidentPOST-MORTEMseverity: high

The Start Script That Hid Eight Servers' Worth of Classes

Symptom
Immediately after a routine deploy, the service failed on every host with Error: Could not find or load main class Main. No traffic was served. Rolling back to the previous release fixed it instantly, which made the new build look guilty even though its classes were fine.
Assumption
The team assumed the build was broken. The JAR had been rebuilt minutes earlier, so reviewers blamed a bad merge that deleted the main class. Two engineers spent an hour diffing commits while the service stayed down on all eight hosts.
Root cause
The release layout placed classes under /opt/shop/classes/com/shop/Main.class, but the new start script ran from /opt/shop/classes/com/shop and called java Main with no -cp flag. The JVM searched the package folder itself instead of the classpath root, so com/shop/Main.class was unreachable. The classes were intact — the working directory and missing -cp hid them.
Fix
The start script was changed to cd to the release root and launch with an explicit classpath: java -cp /opt/shop/app.jar:/opt/shop/lib/* com.shop.Main. A smoke step was added to the pipeline that runs the new start script against a staging host and greps for the ready log line before production rollout continues.
Key lesson
  • A start script is production code — review its cd and -cp lines as carefully as application code, because they decide whether the JVM can see your classes at all.
  • Smoke-test the launch itself, not just the build. A pipeline that compiles but never runs the start command will happily ship a script that points at nothing.
  • Log the effective launch: echo the working directory and full java command at startup so the next incident starts with evidence instead of guesses.
Production debug guideFive lookup failures behind one message — pinpoint yours with these exact commands.5 entries
Symptom · 01
Error names a bare class like Main but your code declares a package
→
Fix
Check where you're standing versus where the tree starts: pwd && ls && find . -maxdepth 4 -name "Main.class". If Main.class sits beside your prompt with no com/ above it, you're inside the package folder. cd up until ls shows the com/ directory, then run java -cp . com.shop.Main from there.
Symptom · 02
Your command line ends in .class and nothing else helps
→
Fix
Look at your own command history — if the command ends in .class, that's the whole bug. Rerun without the suffix: java -cp . Foo. Never pass .class or .java to the java launcher; those suffixes belong to javac and the filesystem, not to class names.
Symptom · 03
Works in the IDE but fails in a terminal or deploy script
→
Fix
Make the classpath explicit and distrust the environment: echo "CLASSPATH=$CLASSPATH" then run java -cp .:lib/ com.shop.Main. If the explicit flag works, your CLASSPATH was stale. Confirm what's loaded with java -cp .:lib/ -verbose:class com.shop.Main 2>&1 | head -20.
Symptom · 04
java -jar reports no main manifest attribute or can't find the class
→
Fix
Read the manifest directly: unzip -p app.jar META-INF/MANIFEST.MF. If no Main-Class line appears, rebuild with jar cfe app.jar com.shop.Main -C out . and verify the class is inside via jar tf app.jar | grep Main. Remember java -jar ignores -cp, so the manifest is the only classpath that counts.
Symptom · 05
Main class starts loading, then a dependency class is missing
→
Fix
The main class loaded but a dependency didn't: rerun with java -verbose:class com.shop.Main 2>&1 | grep -i "not found\|NoClassDefFound" and list the JAR contents with jar tf lib/.jar | grep MissingType. Add the missing JAR to -cp with a quoted lib/ wildcard.
Could Not Find or Load Main Class Causes Compared
Root CauseHow to ConfirmFixPrevention
Launched from inside the package directoryls shows Main.class beside your prompt; cd .. and the com/ tree appearscd to the classpath root and run java -cp . com.shop.MainStart scripts cd to a fixed root before launching
Package statement doesn't match the folder treefind . -name Main.class shows a path that doesn't mirror the packageFix the package line or move the file, recompile with javac -dAlways compile with -d and let javac build the tree
Typed java Foo.class instead of java FooThe command line ends in .classDrop the suffix: java -cp . FooShip a launch script so nobody types the command by hand
Runnable JAR is missing Main-Classunzip -p app.jar META-INF/MANIFEST.MF shows no Main-Class lineRebuild with jar cfe app.jar com.shop.Main -C out .Generate the manifest in Maven or Gradle, verify in CI
⚙ Quick Reference
5 commands from this guide
FileCommand / CodePurpose
classpath-root-demo.shfind out -name "*.class"The Classpath Root Versus the Current Directory
srccomshopMain.javapublic class Main {Package Names Must Match the Folder Tree
srcMain.javapublic class Main {java Foo.class Versus java Foo
classpath-flag-demo.shecho "CLASSPATH=$CLASSPATH"-cp and CLASSPATH
manifest-fix-demo.shjar tf shop.jar | grep -E "Main|MANIFEST"JAR Manifests

Key takeaways

1
java takes a class name like com.shop.Main, never a file name
Foo.class always fails.
2
Run from the classpath root above the package tree, not from inside the package folder.
3
-cp replaces CLASSPATH for one launch; prefer the explicit flag over the environment variable.
4
Package declarations must mirror the folder tree; javac -d builds it correctly for you.
5
Runnable JARs need Main-Class in the manifest, and java -jar ignores -cp.
6
Code that runs in an IDE but not in a terminal is missing an explicit classpath, not broken.

Common mistakes to avoid

5 patterns
×

Typing java Foo.class instead of java Foo

Symptom
The exact error names Foo.class, and no directory layout or manifest change fixes it, because the launcher treats the whole string as a class name.
Fix
Drop the suffix and name the class: java -cp . Foo. If you catch yourself typing .class or .java after java, stop — the launcher only accepts binary class names.
×

Running java from inside the package folder

Symptom
Main.class sits right next to you in the shell, yet java Main fails — the launcher looks for com/shop/Main.class under a root, not beside your prompt.
Fix
cd up to the root that holds the package tree (the folder containing com/) and rerun with the full name: java -cp . com.shop.Main. Hard-code that directory in start scripts.
×

Depending on a stale CLASSPATH environment variable

Symptom
It works on your laptop but fails on the server, because CLASSPATH points somewhere different on each box and -cp was never given.
Fix
Put the classpath on the command line every time: java -cp .:lib/ com.shop.Main. Quote lib/ so the shell passes it through to the JVM untouched.
×

Compiling without javac -d so class files land in the wrong place

Symptom
The .java file declares package com.shop but Main.class sits in src/ with no com/shop around it, so no classpath root can ever resolve it.
Fix
Always compile with javac -d out so the compiler builds the folder tree for you, then launch from out with the full class name.
×

Shipping a JAR with no Main-Class and running java -jar

Symptom
java -jar app.jar reports no main manifest attribute even though the class is inside the JAR — the manifest simply never named it.
Fix
Rebuild with jar cfe app.jar com.shop.Main -C out . so the manifest is written for you, or configure the Maven/Gradle manifest block — then verify with unzip -p.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
Why does java Foo.class fail with this error?
Q02JUNIOR
What does the -cp flag do?
Q03SENIOR
Your class is packaged as com.shop.Main. How must the folders look?
Q04SENIOR
java -jar fails but java -cp works. What's going on?
Q05SENIOR
It runs in IntelliJ but fails on the server. Walk me through your diagno...
Q01 of 05JUNIOR

Why does java Foo.class fail with this error?

ANSWER
Because java takes a class name, not a file name. Foo.class is parsed as class class in package Foo, which doesn't exist. The fix is java -cp . Foo run from the directory that roots the package tree.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
Can I run a Java program with java Foo.class?
02
What is the classpath root, exactly?
03
Does -cp override the CLASSPATH variable?
04
Why is my -cp ignored when I use java -jar?
05
Why does my code run in the IDE but not from the terminal?
06
java -jar says it can't find the main class — where do I start?
N
Naren Founder & Principal Engineer

20+ years shipping production Java in banking & fintech. Lessons pulled from things that broke in production.

Follow
✓ Verified
production tested
September 24, 2026
last updated
1,942
articles · all by Naren
🔥

That's Exception Handling. Mark it forged?

6 min read · try the examples if you haven't

←
Previous
Hibernate LazyInitialization Fix
20 / 20 · Exception Handling
Next
Java Could Not Create JVM Fix
→