Module 1 · Getting Started & Mental Models ⏱ 19 min

JDK vs JRE vs JVM (and Classpath Basics)

By the end of this lesson you will be able to:
  • Explain the difference between the JDK, JRE, and JVM
  • Describe what each component does in the compile-and-run cycle
  • Explain how the classpath tells the JVM where to find compiled classes
  • Diagnose a ClassNotFoundException as a classpath problem, not a code problem

Before you can write a line of Java, you need to know what is actually running it. Java's toolchain is split into three layers — the JDK, the JRE, and the JVM — and each has a distinct job. Most "it works on my machine" disasters trace back to mixing them up: a program that compiles on your laptop refuses to start on a server, or a build fails because the wrong layer was installed. Knowing which layer does what turns those mystery errors into a one-line fix.

JDK — the developer kit

The Java Development Kit (JDK) is everything a developer needs to write, compile, and debug Java. Its headline tool is the compiler javac, which turns your .java source into bytecode. It also ships a launcher (java), a debugger, the javadoc documentation generator, and javap for inspecting compiled classes. Crucially, the JDK contains the JRE and the JVM nested inside it — so installing a JDK gives you the whole stack. If you are writing or building code, this is the layer you install.

JRE — the runtime

The Java Runtime Environment (JRE) is the layer for a machine that only needs to run Java, never build it. It bundles the JVM together with the standard libraries — the ready-made classes every program leans on — but it ships no compiler. With a JRE you can launch an existing .class file; you cannot create one from .java source. A production server that only ever runs your finished app needs exactly this layer and nothing more.

JVM — the engine

The Java Virtual Machine (JVM) is the process that actually executes your bytecode. It is platform-specific — there is a separate JVM build for Windows, macOS, and each Linux distribution — yet the bytecode it consumes is identical everywhere. That asymmetry is Java's founding promise, write once, run anywhere: the same .class produced on one operating system runs unchanged on any other that has a matching JVM. The JVM also verifies bytecode before running it, which blocks a whole family of low-level memory attacks.

flowchart TD
  JDK["JDK (development)"] --> JRE["JRE (runtime)"]
  JRE --> JVM["JVM (execution)"]
  JDK --> DEV["javac · debugger · javadoc"]
  JRE --> LIB["Standard libraries"]
  JVM --> BYTE["Bytecode interpreter / JIT"]
  style JDK fill:#c2410c,color:#fff
  style JRE fill:#c2410c,color:#fff
  style JVM fill:#c2410c,color:#fff
The JDK wraps the JRE, which wraps the JVM. Each layer adds tools the one below does not need.
flowchart LR
  S["Main.java<br/>source code"] --> C["javac<br/>compiler"]
  C --> B["Main.class<br/>bytecode"]
  B --> V["JVM<br/>verifier + JIT"]
  V --> O["program runs"]
  C -.->|"type errors<br/>stop here"| X["build fails"]
  style C fill:#c2410c,color:#fff
  style V fill:#c2410c,color:#fff
  style X fill:#b91c1c,color:#fff
The compile-then-run pipeline. Type errors are caught by javac before the JVM ever starts.

Compile, then run

Here is the journey of one source file. javac reads Main.java, checks every type, and — if all is well — writes Main.class, a file of compact bytecode instructions. Nothing runs yet; you have only built it. Launching java Main hands that bytecode to the JVM, which loads it, confirms it is well-formed, and executes it statement by statement. The JVM also carries a just-in-time compiler that hot-spots busy code into native machine instructions for speed.

Main.java — a small program we will compile and reason about. Real, compiling Java.
public class Main {
    public static void main(String[] args) {
        String tool = "javac";
        int year = 1995;
        System.out.println("Tool: " + tool);
        System.out.println("Java appeared in " + year);
    }
}

One source file, several classes

A single .java file can declare more than one class, though only one may be public and it must share the file's name. Feed that file to javac and you get a separate .class for every class — not one merged file. That one-to-one mapping between classes and bytecode files is exactly the set of files the classpath later searches through.

Main.java — two classes in one source file. javac would emit Main.class and Greeter.class.
public class Main {
    public static void main(String[] args) {
        Greeter g = new Greeter("Java");
        g.sayHi();
    }
}

class Greeter {
    String who;

    Greeter(String who) {
        this.who = who;
    }

    void sayHi() {
        System.out.println("Hi from " + who);
    }
}

The classpath

When the JVM starts, it needs to know where your compiled .class files live. The classpath is a list of directories and JAR files the JVM searches whenever it encounters a class name. By default the classpath is the current directory, which is enough for a single throwaway file. As soon as you organise code into packages or pull in external libraries, you must set the classpath explicitly — either with the -cp flag on the command line or the CLASSPATH environment variable.

flowchart TD
  CMD["java -cp build Main"] --> Q{"is Main.class<br/>on the classpath?"}
  Q -->|"yes — found in build/"| OK["class loaded, runs"]
  Q -->|"no — wrong folder"| MISS["ClassNotFoundException"]
  style Q fill:#3776ab,color:#fff
  style OK fill:#15803d,color:#fff
  style MISS fill:#b91c1c,color:#fff
The JVM only finds a class if its .class file sits somewhere on the classpath it was given.

Why classpath errors bite at runtime

The classpath is searched lazily, only when the JVM first needs a class it has not yet loaded. That is why a program can build perfectly yet crash on launch: javac found everything at compile time using one classpath, but java is handed a different one and cannot locate a class it needs. With packages the rule is strict and mechanical — a class declared as com.example.app.Main must live at com/example/app/Main.class relative to a classpath root. Move the file, or forget the root, and the JVM will not find it no matter how correct the code is.

Main.java — a class in a package. It must sit in a matching com/example/app/ directory tree.
package com.example.app;

public class Main {
    public static void main(String[] args) {
        System.out.println("running from a package");
    }
}
Exercise

What does this program print? Read it carefully and type the exact output (two lines).

public class Main {
    public static void main(String[] args) {
        String tool = "javac";
        int year = 1995;
        System.out.println("Tool: " + tool);
        System.out.println("Java appeared in " + year);
    }
}
Exercise

Method-driven output. This program calls a helper method twice and uses each result differently. Predict both lines.

public class Main {
    static String toolchain() {
        return "jdk -> jre -> jvm";
    }

    public static void main(String[] args) {
        System.out.println(toolchain().toUpperCase());
        System.out.println(toolchain().length());
    }
}
Exercise

Which component includes the compiler javac?

Exercise

Your project compiles cleanly with javac, but at runtime java -cp build Main throws NoClassDefFoundError for a helper class. What is the most likely cause?

Exercise

You compile Game.class on Linux and copy only that .class file to a Windows machine with a JVM installed. It runs without recompiling. What makes that possible?

Recap

  • The JDK builds code (javac, debugger); it contains the JRE and the JVM.
  • The JRE runs finished bytecode plus the standard libraries, but ships no compiler.
  • The JVM executes bytecode — platform-specific, while the bytecode itself is not.
  • javac turns one .java into .class; one source file can declare several classes, each becoming its own .class.
  • The classpath is where the JVM looks for classes — set it with -cp, and packages must mirror the directory structure.
  • A ClassNotFoundException at runtime points at the classpath, not the code.

Next you will open a class of your own and start writing the methods that live inside it.

Checkpoint quiz

What does the classpath tell the JVM?

A server has the JRE installed but not the JDK. Can it run a compiled Java program?

Go deeper — technical resources