Before you write any code, it helps to understand what builds and runs it. Java has a small set of tools around the language, and nothing works without them. We'll take them in order — from what you install on your machine to the build tools that fetch dependencies for you.
The javac compiler turns the source into bytecode, jar packs the classes into an archive. At run time the JVM looks up every class on the classpath, and java -jar puts only that one archive there: library classes stay in the build tool's cache and are never found — hence NoClassDefFoundError on the very first use.
JDK, JRE and JVM — who is who
Three acronyms that get confused most often — here is what's inside each.
JVM (Java Virtual Machine) is the program that executes compiled Java code. You write .java, the compiler turns it into bytecode (.class), and the JVM runs that bytecode. The JVM is what gives Java its famous "write once, run anywhere": the bytecode is the same everywhere, and each operating system has its own JVM.
JRE (Java Runtime Environment) is the JVM plus the standard libraries (collections, file and network handling, and so on). This set is enough to run finished Java programs, but not enough to compile them.
JDK (Java Development Kit) is the JRE plus development tools: the javac compiler, a debugger, and more. For development you need the JDK.
A short formula: JDK = JRE + development tools, JRE = JVM + libraries.
These days almost no one installs a standalone JRE — you download a JDK (a Temurin, Liberica or Amazon Corretto build, say) and work with it.
Compiling and running by hand
Worth building a program by hand once, to feel what build tools do under the hood.
Suppose you have a file Hello.java:
live example
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, Java!");
}
}
Run
Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →
Compile it with javac — it creates Hello.class with the bytecode:
javac Hello.java
Run it with the java command (the class name, without the .class extension):
java Hello
A simple case needs no separate compilation step at all — java Hello.java compiles and runs in one go. Any Java from 11 onwards can do that, and from 22 it also works for programs spread over several files. Handy for experiments, but real projects are still built the full way.
classpath — where to look for classes
As soon as there is more than one class and third-party libraries appear, the JVM must be told where to find them. That's the classpath — a list of folders and archives that contain .class files.
# run a class, telling the JVM to look for classes in the out folder and in the lib/some.jar library
java -cp out:lib/some.jar Hello
The separator depends on the system: a colon : on Linux and macOS, a semicolon ; on Windows. You rarely assemble a classpath by hand — the build tool does it; understanding it matters mainly for errors like ClassNotFoundException (the class wasn't found on the classpath).
The classpath is no abstraction: the JVM keeps it as a string and searches it for every class. This program prints its own and shows what happens to a class that isn't there:
live example
public class ClasspathDemo {
public static void main(String[] args) {
System.out.println("JVM version: " + System.getProperty("java.version"));
System.out.println("classpath: " + System.getProperty("java.class.path"));
System.out.println("own class: " + ClasspathDemo.class.getName());
try {
Class.forName("com.google.common.collect.ImmutableList");
System.out.println("guava found");
} catch (ClassNotFoundException e) {
System.out.println("not on the classpath: " + e.getMessage());
}
}
}
Run
Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →
Guava is not there, so instead of a loaded class you get ClassNotFoundException with its name — exactly what the log shows when the build tool didn't put the library next to your code.
What is a jar
Scattering hundreds of .class files around is inconvenient. A jar (Java ARchive) is an ordinary zip archive containing .class files and accompanying metadata. One file instead of a pile of classes: easy to share, add as a dependency and run.
If a jar declares an entry point (a main class), you can run it directly:
java -jar app.jar
The libraries you add also arrive as jar files, and your own project most often turns into a jar at the end of a build.
java -jar and dependencies
java -jar app.jar puts only that archive on the classpath (plus whatever its manifest lists under Class-Path). An ordinary jar from Gradle or Maven holds just your own classes: the dependencies stay in the build tool's cache. Hence the classic picture — gradle run works, while java -jar on the server dies with NoClassDefFoundError: com/fasterxml/jackson/...: the class was there at compile time and missing at run time.
Three ways out. A "fat" jar with every dependency inside — bootJar in Spring Boot or the shadow plugin; one file, the simplest rollout. An explicit classpath: copy the dependencies into lib/ and run java -cp "app.jar:lib/*" com.example.Main. Or a Docker image, where the build puts application and libraries in as layers — the cloud standard.
Maven and Gradle — why you need build tools
While your project is a single file, javac and java are enough. A real application is dozens of your own classes plus third-party libraries that have their own dependencies. Downloading all that by hand, setting up the classpath and tracking versions is painful, so build tools take the work on: they download dependencies, compile the code, run the tests and assemble the final jar.
The Java world has two main build tools — Maven and Gradle. Both do the same thing; they differ in how the project is described.
Dependency coordinates. Any Java library is uniquely described by three parts: groupId (who published it, usually a reverse domain), artifactId (which library it is) and version. This triple is its coordinates: with them the build tool finds and downloads the right jar from a repository (Maven Central by default).
Maven describes a project in a pom.xml file (XML). A dependency looks like this:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.2.1-jre</version>
</dependency>
Gradle describes a project in build.gradle (Groovy) or build.gradle.kts (Kotlin). The same dependency — one line by coordinates:
dependencies {
implementation("com.google.guava:guava:33.2.1-jre")
}
The build command is short too: mvn package for Maven, gradle build for Gradle (or ./gradlew build via the wrapper, which installs the right Gradle version itself).
Basic project structure
By default Maven and Gradle expect the same folder layout — and almost all ecosystem code follows it:
my-app/
├── pom.xml # or build.gradle(.kts)
└── src/
├── main/
│ ├── java/ # application source code
│ └── resources/ # config files, templates, etc.
└── test/
├── java/ # test code
└── resources/ # test data
The main rule: production code goes in src/main, tests in src/test. The build tool knows these paths and needs no configuration. The build output lands in a separate folder (target/ for Maven, build/ for Gradle) that is not committed to version control.
Managing your JDK version
The JDK is released regularly, and a single machine often needs several versions: one project on Java 17, another on Java 21. Version managers keep them side by side and switch.
- SDKMAN! is a popular tool for Linux and macOS. It installs and switches JDKs (and Gradle/Maven too) with one command:
sdk install java 21.0.3-tem, thensdk use java 21.0.3-tem. - Which version is active is decided by
JAVA_HOME(the path to the JDK you want) andPATH;java -versionandjavac -versionshow the one in use right now.
Worth knowing separately: LTS versions (Long-Term Support) — releases with long support (17, 21, …). Production projects usually pick exactly these: security updates keep coming for them longer.
In short
- The JVM executes bytecode, JRE = JVM + libraries (running only), JDK = JRE + tools (you need the JDK for development).
javac Hello.javacompiles the source into.classbytecode,java Helloruns it.- The classpath is a list of paths where the JVM looks for classes; a
ClassNotFoundExceptionmeans the class wasn't found there. - A jar is a zip archive of classes; a finished application is run as
java -jar app.jar. - Maven (
pom.xml) and Gradle (build.gradle) download dependencies by coordinatesgroupId:artifactId:versionand build the project. - The standard layout is the same: code in
src/main/java, tests insrc/test/java. - Several JDK versions live side by side via SDKMAN!; for work you pick LTS versions.
What to read next
- Syntax and data types — what the code you compile is made of.
- Exceptions — how Java reports errors at runtime.
- OOP in Java — classes, objects and how programs are built from them.
- Garbage collection in Java — what the JVM does with memory at run time and what
-Xms/-Xmxcontrol.