An IDE is a convenience, not a requirement. Every Java program in production is compiled with javac and started with java from a terminal or a build script, and understanding those two commands removes the fog that an IDE puts between you and the toolchain.
What are the two commands?
javac is the compiler: it reads .java source and writes .class files. java is the launcher: it reads .class files and runs them. The rule that catches newcomers is that java never compiles source code, and javac never runs your program.
# compile every .java file in the current folder
javac *.java
# run a class by its fully qualified name
java com.example.Greeting
Notice which name you give to java: not the file name, but the class name, and with the package prefix if there is one. That detail becomes obvious as soon as you organise files into packages.
How do I control where the class files go?
By default javac drops the .class file next to its source, which gets messy fast. The -d flag sends all output into a directory you choose, and it becomes the classpath you later hand to java.
mkdir -p out
javac -d out src/com/example/Greeting.java src/com/example/Util.java
java -cp out com.example.Greeting
Two paths, two different jobs. -d out says where to write the compiled classes; -cp out says where to look for them at runtime. Keeping sources in one folder and classes in another is the layout every build tool uses, and adopting it early makes the classpath concept concrete.
How do packages line up with folders?
A package declaration in the source must match the folder structure below the source root. A file declaring package com.example; belongs in com/example/, and the compiler refuses to build the project otherwise.
# project layout
src/com/example/Greeting.java -> package com.example;
src/com/example/util/Helper.java -> package com.example.util;
# compile the whole tree
javac -d out $(find src -name '*.java')
java -cp out com.example.Greeting
Run the program from a directory that is not the source root and the same classpath works, because the classpath is absolute with respect to wherever you launched from. What must always be true is that the package path and the folder path agree — that is the whole rule.
Can I run a file without compiling it first?
Yes. Passing a source file straight to java compiles it in memory and runs it, which is the fastest way to try a snippet.
java Greeting.java
java --source 21 Greeting.java
This mode exists for experimentation. It does not produce class files, it does not support a multi-file project in one go, and it hides the compile step you need to understand. Use it to check a fragment, then go back to the two-step flow.
Why do I get "ClassNotFoundException" or "Could not find or load main class"?
Both messages mean the same thing: the launcher could not locate the class you named. The causes, in order of frequency, are a wrong name (a missing package prefix), a classpath that does not point at the compiled output, and compiling into one directory while running with the classpath set to another.
# list what actually got compiled
find out -name '*.class'
# check the classpath the JVM sees
java -XshowSettings:properties -cp out com.example.Greeting 2>&1 | grep -i class.path
Then compare the directory you listed with the one you passed to -cp. If the class file is at out/com/example/Greeting.class, the classpath must be out and the class name must be com.example.Greeting — the package path is part of the class name, and the classpath is the root that package path hangs from.
How do you ship a program as a single file?
Compiled classes are loose files, which is fine for learning and awkward for distribution. The standard packaging format is a JAR: a zip archive of class files plus a manifest that names the class to start. Once you have one, a single command runs it on any machine with a compatible runtime, and that is the shape most Java applications are delivered in.
# 1. compile into a directory
javac -d out src/com/example/*.java
# 2. package the classes plus a manifest
jar --create --file app.jar --main-class com.example.Greeting -C out .
# 3. run it
java -jar app.jar
# 4. look inside
jar --list --file app.jar
# what the archive contains
META-INF/
META-INF/MANIFEST.MF
com/
com/example/
com/example/Greeting.class
com/example/Util.class
The --main-class option writes the entry point into the manifest, which is what lets java -jar know where to begin without you naming a class. Adding dependency jars works the same way: they go into the archive too, and the manifest's class-path entry lists them, or you place them next to the jar and name them explicitly.
How do you read a compiler error that mentions a symbol?
"Cannot find symbol" is the most common compiler error and it has two usual causes: the name is misspelled or cased differently, or the class that declares it is not imported. The message names the symbol and the location, and it usually lists the closest matches it could find — that list is often the answer sitting in plain sight.
Greeting.java:6: error: cannot find symbol
System.out.println(name.toUppercas());
^
symbol: method toUppercas()
location: variable name of type String
When the symbol is a type rather than a method, the fix is an import or a fully qualified name. When it is a method, the fix is usually a typo — and the compiler's suggestion list often contains the correctly spelled version.
Quick reference
javaccompiles,javaruns. They never swap roles.-dchooses the output directory;-cpchooses where to look for classes.- A package declaration must match the folder path below the source root.
java File.javaruns a single file for quick experiments.- Package prefix plus class name is what you pass to
java. - "Class not found" always means: wrong name, or wrong classpath root.
Try it yourself: create a two-class project with a com.example package, compile it with javac -d out, then run it from three different working directories using the classpath. Finally, rename the output directory and watch the class-not-found error appear — then fix it by pointing the classpath at the right place.