beginner18 min

The JVM, the JDK, and How a Java Program Runs

What actually happens between writing Java source code and seeing a program run — and why that model makes Java portable.

What you'll learn

  • Explain the difference between the JDK, the JVM, and bytecode
  • Describe what javac and java each do, in order
  • Read and predict the structure of a small multi-class Java program

Explanation

Java code goes through two distinct steps before it does anything: compilation, then execution — and understanding what happens in each step explains most of what makes Java behave the way it does. The JDK (Java Development Kit) is the toolset you install: it bundles a compiler (javac), the runtime needed to execute programs, and supporting tools. When you run javac Greeter.java, the compiler doesn't produce machine code for your specific CPU — it produces bytecode, a compact, platform-neutral instruction format saved in a .class file. That bytecode is not directly executable by your operating system; it's executable by the JVM (Java Virtual Machine), a program that reads bytecode and either interprets it or compiles pieces of it to real machine code on the fly (the just-in-time, or JIT, compiler) as your program runs.

This two-step model — compile once to bytecode, run that same bytecode on any JVM — is the literal mechanism behind Java's "write once, run anywhere" promise. The .class file you produce on one operating system runs unmodified on any other operating system that has a matching JVM installed, because the JVM is what absorbs the platform-specific differences, not your compiled code. The command java Greeter starts a JVM, loads Greeter.class, and calls its public static void main(String[] args) method — the fixed entry point every runnable Java program needs.

A Java source file has a small number of required, position-sensitive parts: an optional package declaration first, then any import statements, then exactly one public top-level class whose name must match the filename exactly (Greeter.java must contain public class Greeter) — this is a compiler-enforced rule, not a convention. A single file can contain more than one class, but only one may be public; the compiler produces a separate .class file for every class, public or not, which is why a small multi-file program still produces several .class files.

Example

This models the compile-then-run pipeline as data, not real Java — the real syntax appears in this lesson's guided local lab.

// A simplified model of javac + java, as a two-stage pipeline.
function compile(sourceFileName, sourceContainsPublicClass) {
  if (!sourceContainsPublicClass) throw new Error("no public class found");
  return { bytecodeFile: sourceFileName.replace(".java", ".class") };
}

function run(bytecodeFile) {
  return `JVM loads ${bytecodeFile} and calls its main method`;
}

const compiled = compile("Greeter.java", true);
console.log(run(compiled.bytecodeFile));
// -> "JVM loads Greeter.class and calls its main method"

Try it yourself

Change the filename so it no longer matches the public class name, and predict what compile() should do.

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Guided exercise

Guided exercise

Write classFileFor(sourceFileName) that returns the .class filename javac would produce for a source file's PUBLIC class — i.e. it strips '.java' and appends '.class'. Assume the input always ends in '.java'.

Checks: Greeter.java -> Greeter.class · Main.java -> Main.class

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Stuck? Get a hint.

Independent exercise

Independent exercise

Write findEntryPoint(classNames, publicClassName) that returns publicClassName if it appears in classNames (the JVM can only start from the class you name on the command line, and it must exist), otherwise returns null.

Checks: returns the class name when present · returns null when the class isn't in the list

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Stuck? Get a hint.

Guided local lab

Compile and Run a Multi-Class Java Application Locally

Runs on your computer
This lab runs on your own computer, in your own terminal and editor — not in your browser. VisaSparkSchools does not execute, run, or verify these commands for you. Follow the verification steps yourself to confirm your result.

You'll write two classes — a Greeter that builds messages and a Main that calls it — compile both with javac, and run the program with java, entirely on your own machine.

Required tools

  • JDK (21 LTS or newer)
  • A terminal (any)
  • A text editor or IDE (any (e.g. VS Code, IntelliJ IDEA Community))

Setup

  1. Confirm your JDK is installed: run `java -version` and `javac -version` — both should print 21 or higher.
  2. Create a project folder named greeting-app.
  3. Inside it, create a src folder for your .java source files.

Project structure

greeting-app/
  src/
    Greeter.java
    Main.java

Starter files

src/Greeter.java

public class Greeter {
    // TODO: add a static method buildMessage(String name) that returns
    // "Hello, " + name + "!" -- and call it from Main.
}

src/Main.java

public class Main {
    public static void main(String[] args) {
        // TODO: call Greeter.buildMessage(...) with a name and print the result.
        // TODO: also print how many command-line arguments were passed (args.length).
    }
}

Requirements

  • Greeter.java defines a public class Greeter with a static method buildMessage(String name) returning a greeting string.
  • Main.java defines the program's entry point and calls Greeter.buildMessage(...).
  • Running the program with at least one command-line argument prints that argument's count.
  • The program compiles with zero warnings and runs without throwing.

Commands to run

  • Compile both source files into .class files

    javac -d out src/Greeter.java src/Main.java
  • Run the compiled program, passing one argument

    java -cp out Main Alex

Expected behavior

javac produces Greeter.class and Main.class inside out/ with no errors. Running `java -cp out Main Alex` prints a greeting containing "Alex" and a line reporting 1 argument.

Verify it yourself

  • ls out

    Expected: Greeter.class and Main.class are both listed

  • java -cp out Main Alex

    Expected: Prints a greeting mentioning Alex, then a line stating 1 argument was passed

  • java -cp out Main

    Expected: Prints a greeting (using a default or empty name) and reports 0 arguments, without crashing

Troubleshooting

  • `error: class Greeter is public, should be declared in a file named Greeter.java`The public class name must exactly match its filename, including case.
  • `Error: Could not find or load main class Main`Make sure you pass -cp out (the folder containing the .class files) and the bare class name Main, not Main.java or out/Main.class.
  • `javac: file not found: src/Greeter.java`Run javac from the greeting-app folder, not from inside src/.

Stuck? Get a hint.

Extension challenge

Add a third class, Farewell, with a static method buildFarewell(String name), and call it from Main right after the greeting so the program prints both a hello and a goodbye.

When you've verified this locally, use the "Mark lesson complete" button below to record your progress.

Common mistakes

  • Naming the file differently from its public class (Java requires an exact, case-sensitive match).
  • Running `java Main.java` instead of `java Main` — java takes a class name, not a filename (a newer single-file launch mode accepts .java files directly, but that's a different mechanism than the standard compile-then-run workflow this lesson teaches).
  • Forgetting that every class you reference must itself be compiled — a NoClassDefFoundError almost always means a needed .class file is missing from the classpath.

Knowledge check

Knowledge check

1. What does javac produce from a .java source file?
2. A file named Widget.java contains `public class Gadget { ... }`. What happens when you compile it?
3. Why can the same .class file run unmodified on Windows, macOS, and Linux?

Takeaway

Java source compiles to platform-neutral bytecode, and a JVM — one per platform — is what actually executes it; that split is the entire mechanism behind Java's portability.

Summary

javac turns .java source into .class bytecode; java starts a JVM that loads that bytecode and runs its main method. A public class's name must match its filename exactly, and every referenced class needs its own compiled .class file.

References

Your notes

Notes save automatically.

Finished this lesson?

Mark it complete to track your progress and schedule a future review.