JDK vs JRE vs JVM Explained — What Every Java Developer Must Know
Every time you install Java, you're faced with a choice: do you download the JDK or the JRE? Most beginners just pick one at random and hope for the best. That guesswork trips you up the moment something breaks — like trying to compile code and getting a 'javac not found' error — because you installed the wrong thing. Understanding the difference isn't just academic trivia; it's the foundation for every Java project you'll ever set up.
Java was built around one revolutionary promise: write your code once, run it literally anywhere — Windows, Mac, Linux, a smart fridge. That promise only holds because of a clever three-layer architecture: the JVM handles the 'run anywhere' magic, the JRE bundles everything needed to support it at runtime, and the JDK gives developers all the tools to build Java programs in the first place. Each layer has a specific job, and they nest inside each other like Russian dolls.
By the end of this article you'll know exactly what each acronym means, how the three components relate to each other, which one to install for your use case, and you'll be able to explain the whole thing confidently in a job interview. No handwaving — we're going to trace a real Java file from source code all the way to running output, so you can see every layer doing its job.
The JVM — The Engine That Runs Your Java Code Anywhere
The Java Virtual Machine (JVM) is a program that runs on your computer and pretends to be a standardised, imaginary computer. Your Java code doesn't run directly on your CPU — it runs on this imaginary machine. That's the secret behind Java's 'write once, run anywhere' guarantee.
Here's why that matters. Windows, macOS, and Linux all have different CPUs and operating system rules. Normally, software compiled for Windows won't run on a Mac. Java sidesteps this by compiling your code into a neutral format called bytecode — instructions for the imaginary JVM machine, not for any real CPU. Every OS has its own version of the JVM, and each one knows how to translate that bytecode into whatever the local OS understands.
Think of bytecode as a universal recipe written in a language every chef (JVM) speaks, even though the actual kitchen equipment (CPU/OS) differs from country to country. The JVM is also responsible for memory management — it automatically cleans up objects your program no longer needs, through a process called garbage collection. You don't free memory manually in Java; the JVM handles it.
Critically, the JVM only understands bytecode. It cannot read your original .java source file. Something has to compile that source file first — and that's where the other two components come in.
// This file is your SOURCE CODE — written by a human, readable by humans. // The JVM cannot run this directly. It must be compiled to bytecode first. public class HelloJVM { public static void main(String[] args) { // This message proves our code reached the JVM and executed. System.out.println("Hello from the JVM!"); // The JVM knows which operating system it is running on. // We can ask it at runtime — notice we never wrote OS-specific code. String operatingSystem = System.getProperty("os.name"); System.out.println("Running on: " + operatingSystem); // The JVM also tells us the Java version it is using. String javaVersion = System.getProperty("java.version"); System.out.println("Java version: " + javaVersion); } }
Running on: Mac OS X
Java version: 17.0.9
The JRE — The Complete Runtime Package Your Program Needs to Run
The Java Runtime Environment (JRE) is the JVM plus a large library of pre-written code that Java programs rely on at runtime. You can think of the JRE as the JVM bundled with its toolbox.
When your Java program says System.out.println(...), it's not magic — System, out, and println are all classes and methods written by the Java team, pre-compiled, and shipped as part of the JRE. This collection of pre-written classes is called the Java Class Library (or the Java API). It contains thousands of ready-made tools: ways to read files, connect to the internet, format dates, sort lists, and much more.
Without the JRE, the JVM would be like a car engine with no wheels, seats, or fuel system — technically impressive but not going anywhere.
Before Java 9, you could install the JRE standalone — perfect for end users who just wanted to run a Java app, not build one. From Java 9 onwards, Oracle merged the JRE into the JDK for distribution purposes. But the conceptual distinction still exists and still matters, especially when you're creating a minimal production deployment using the jlink tool, which lets you bundle only the JVM + the specific library modules your application actually uses, making your deployment tiny and fast.
// This example uses classes from the Java Class Library — the part of the JRE // that isn't the JVM itself. We didn't write ArrayList, LocalDate, or Collections. // They came pre-packaged in the JRE. The JVM executes them just like our own code. import java.util.ArrayList; // From the JRE's java.util library import java.util.Collections; // Also from java.util import java.time.LocalDate; // From the JRE's java.time library (added in Java 8) public class JRELibraryDemo { public static void main(String[] args) { // ArrayList is a resizable list — provided by the JRE, not written by us. ArrayList<String> programmingLanguages = new ArrayList<>(); programmingLanguages.add("Java"); programmingLanguages.add("Python"); programmingLanguages.add("Rust"); programmingLanguages.add("Go"); // Collections.sort() is also a JRE utility — alphabetically sorts our list. Collections.sort(programmingLanguages); System.out.println("Sorted languages: " + programmingLanguages); // LocalDate.now() asks the JRE to fetch today's date using the OS clock. LocalDate today = LocalDate.now(); System.out.println("Today's date from JRE: " + today); // We can also demonstrate the JRE's string formatting utilities. String message = String.format("There are %d languages in the list.", programmingLanguages.size()); System.out.println(message); } }
Today's date from JRE: 2024-03-15
There are 4 languages in the list.
The JDK — The Full Developer Toolkit That Contains Everything
The Java Development Kit (JDK) is the complete package. It contains the JRE (which contains the JVM), plus a set of developer tools you need to actually build Java programs. The most important tool is javac — the Java compiler. It's the program that reads your .java source file and outputs a .class bytecode file that the JVM can then execute.
Other tools bundled in the JDK include: javadoc (generates HTML documentation from your code comments), jar (packages your compiled classes into a single distributable archive file), jdb (a command-line debugger), jshell (an interactive console introduced in Java 9, great for experimenting), and jlink (packages a minimal JRE for your specific app).
The relationship is simple nesting: JDK ⊃ JRE ⊃ JVM. The JDK is the outermost layer. It contains everything. If you're writing Java code — which you are, since you're reading this — install the JDK. Full stop.
When you run javac HelloJVM.java in your terminal, you're using a JDK tool. When you then run java HelloJVM, you're using the JVM inside the JRE inside the JDK. Two different tools in the same box, each doing a distinct job in the pipeline from source code to running program.
// STEP 1 — You write this source code and save it as CompilationPipelineDemo.java // STEP 2 — You run: javac CompilationPipelineDemo.java (JDK tool: the compiler) // This produces: CompilationPipelineDemo.class (bytecode — not human-readable) // STEP 3 — You run: java CompilationPipelineDemo (JVM inside JRE inside JDK) // The JVM loads the .class file and executes the bytecode. public class CompilationPipelineDemo { public static void main(String[] args) { // This line only runs because ALL THREE layers did their job: // JDK compiler turned our .java into .class bytecode. // JRE provided the System class and its out.println method. // JVM executed the bytecode instruction that calls println. System.out.println("All three layers (JDK, JRE, JVM) worked together to print this!"); // We can prove the JDK compiled this by checking the class file version. // The number maps to a Java version: 61 = Java 17, 55 = Java 11, 52 = Java 8. int classMajorVersion = CompilationPipelineDemo.class.getPackage() == null ? 0 : 0; // Package check placeholder // A more reliable way — ask the JVM what version compiled this class. System.out.println("Class file compiled with Java spec version: " + System.getProperty("java.class.version")); System.out.println("JDK version used to compile: " + System.getProperty("java.version")); System.out.println("JVM vendor running this code: " + System.getProperty("java.vendor")); } }
Class file compiled with Java spec version: 61.0
JDK version used to compile: 17.0.9
JVM vendor running this code: Eclipse Adoptium
How JDK, JRE, and JVM Work Together — Tracing One Java Program End to End
Let's tie it all together by following a single Java file through its complete lifecycle. This is the story you should be able to tell from memory.
You open your editor and write Greeting.java. At this point nothing has happened yet — it's just a text file. You run javac Greeting.java. The javac compiler (a JDK tool) reads your source code, checks it for syntax errors, and if everything's fine, produces Greeting.class. This .class file contains bytecode — compact, platform-neutral instructions that no human CPU understands natively, but every JVM does.
Now you run java Greeting. The java launcher (part of the JRE) starts the JVM and hands it your .class file. The JVM's class loader finds Greeting.class and loads it into memory. The JVM then runs a Just-In-Time (JIT) compiler that translates the bytecode into native machine code for your specific CPU — this happens at runtime, which is why Java 'warms up' over time and gets faster the longer it runs. Finally, your main method executes, the JRE's System.out.println does its job, and you see output in the terminal.
Three layers. One seamless pipeline. Understanding this flow means you can diagnose almost any Java setup problem on your own.
// Full end-to-end example — write this, compile it, run it, and trace every layer. // // COMPILE: javac Greeting.java <-- JDK's javac tool is used here // RUN: java Greeting <-- JVM (inside JRE inside JDK) takes over here public class Greeting { // The JVM always looks for a method with EXACTLY this signature as the entry point. // 'public' — callable from outside. 'static' — no object needed to call it. // 'void' — returns nothing. 'String[] args' — command-line arguments passed in. public static void main(String[] args) { String recipientName = "Java Developer"; // System — a class provided by the JRE's java.lang library (auto-imported) // .out — a static field on System; it's a PrintStream object // .println() — a method on PrintStream that writes a line to the console System.out.println("Hello, " + recipientName + "!"); // Demonstrating that the JVM manages memory for us. // We create an object — the JVM's garbage collector will clean it up // automatically when this method ends and the reference goes out of scope. StringBuilder messageBuilder = new StringBuilder(); messageBuilder.append("JDK compiled me. "); messageBuilder.append("JRE provided StringBuilder. "); messageBuilder.append("JVM is running me right now."); System.out.println(messageBuilder.toString()); // When main() returns, the JVM will shut down cleanly. // No manual memory cleanup needed — that's garbage collection at work. } }
JDK compiled me. JRE provided StringBuilder. JVM is running me right now.
| Feature / Aspect | JVM | JRE | JDK |
|---|---|---|---|
| Full name | Java Virtual Machine | Java Runtime Environment | Java Development Kit |
| Primary job | Execute bytecode on your specific OS/CPU | Provide the runtime — JVM + standard libraries | Provide everything for building Java programs |
| Contains | Bytecode interpreter + JIT compiler + GC | JVM + Java Class Library (java.util, java.io, etc.) | JRE + javac + javadoc + jar + jdb + jshell + jlink |
| File it works with | .class bytecode files only | .class bytecode files + library .jar files | .java source files (compiles them to .class) |
| Who needs it | Everyone running Java | Anyone running a Java app (end users, servers) | Developers writing Java code |
| Can compile .java files? | No | No | Yes — via the javac command |
| Can run .class files? | Yes — that's its whole job | Yes — via the java launcher command | Yes — JRE is included inside the JDK |
| Installed standalone? | Bundled inside JRE, not installed alone | Pre-Java 9 yes; post-Java 9 merged into JDK | Yes — single download covers all three |
| Platform-specific? | Yes — different JVM per OS/CPU | Yes — ships with the OS-specific JVM | Yes — download the JDK for your specific OS |
| Typical size | ~50 MB (core JVM only) | ~100–200 MB (JVM + libraries) | ~200–400 MB (everything) |
🎯 Key Takeaways
- The JVM is the engine that executes Java bytecode — it's platform-specific (different JVM for Windows, Mac, Linux) so your bytecode doesn't have to be.
- The JRE = JVM + the Java Class Library. It's everything needed to RUN a Java program. Every
import java.*you write pulls from the library the JRE provides. - The JDK = JRE + developer tools (javac, jar, javadoc, jshell). If you're writing Java, install the JDK — it includes the JRE so you never need both.
- The compilation pipeline is: .java source → javac (JDK tool) → .class bytecode → JVM loads and JIT-compiles → native CPU execution. Knowing this flow lets you diagnose virtually any Java environment problem.
⚠ Common Mistakes to Avoid
- ✕Mistake 1: Installing only the JRE and then trying to compile code — Symptom: you run
javac HelloWorld.javaand get'javac' is not recognized as an internal or external command(Windows) orcommand not found: javac(Mac/Linux) — Fix: uninstall the JRE-only distribution and download the full JDK from adoptium.net or oracle.com. The JDK includes the JRE, so you never need both installed separately. - ✕Mistake 2: Compiling with a newer JDK than the JVM version on the target machine — Symptom: the code compiles fine on your laptop but throws
java.lang.UnsupportedClassVersionError: Unsupported major.minor version 61.0when deployed to a server — Fix: compile with a target flag that matches the server's Java version. For example, if the server runs Java 11, compile withjavac --release 11 MyApp.java. This tells the JDK to produce bytecode compatible with Java 11 even if your local JDK is version 17. - ✕Mistake 3: Confusing the JVM with the Java language itself — Symptom: this causes real confusion when you hear that Kotlin, Scala, and Groovy 'run on the JVM' — the misconception is that the JVM is Java-specific — Fix: understand that the JVM only cares about bytecode, not which language produced it. Kotlin's compiler produces the same
.classbytecode format that Java's compiler produces. The JVM runs all of it without knowing or caring which language was used. Java the language and the JVM are separate things that happen to be developed together.
Interview Questions on This Topic
- QCan you explain the relationship between the JDK, JRE, and JVM? Which one contains which?
- QIf a user just wants to run a Java application on their machine but has no interest in coding, what should they install and why?
- QWhat is bytecode, and why does its existence mean Java programs can run on any operating system without recompiling?
Frequently Asked Questions
Do I need to install both JDK and JRE?
No. The JDK already includes the JRE inside it. If you're a developer writing Java code, just install the JDK and you're covered. Installing both separately is redundant and can sometimes cause version conflicts if they're different versions.
What is bytecode in Java and why does it matter?
Bytecode is the intermediate format that javac compiles your .java source code into. It's stored in .class files and is not specific to any CPU or operating system. The JVM on any platform reads this bytecode and translates it into the native instructions for that machine. This is the mechanism that makes Java's 'write once, run anywhere' promise real.
If Java 9+ merged the JRE into the JDK, is the JRE concept still relevant?
Conceptually, yes — absolutely. The JRE as a concept (JVM + standard libraries) still exists inside every JDK; Oracle just stopped releasing it as a separate standalone download. The distinction matters when you use tools like jlink to build a custom minimal runtime for production deployments, or when you're reading older documentation and job descriptions. You'll still see JRE mentioned constantly in the industry.
Written and reviewed by senior developers with real-world experience across enterprise, startup and open-source projects. Every article on TheCodeForge is written to be clear, accurate and genuinely useful — not just SEO filler.