The JDK (Java Development Kit) is the full toolkit we use to write and build Java programs, the JRE (Java Runtime Environment) is the part that only runs them, and the JVM (Java Virtual Machine) is the engine inside that runtime that executes the compiled bytecode. So the JDK contains a Java runtime, and every Java runtime contains a JVM.
The difference between JDK, JRE and JVM decides what we install where. We install the JDK on developer machines and build servers because it has the compiler, and we ship only a runtime to production servers, containers and user desktops. Since Java 11, Oracle no longer publishes a separate JRE, so a modern runtime is either a vendor JRE build or a custom image made with jlink.
The following example asks the running Java 25 program which JVM and runtime it uses, and whether a compiler is available, which is the one thing a plain runtime lacks.
String jvm = System.getProperty("java.vm.name"); // "OpenJDK 64-Bit Server VM"
String runtime = System.getProperty("java.runtime.name"); // "OpenJDK Runtime Environment"
boolean jdk = ToolProvider.getSystemJavaCompiler() != null; // true on a JDK, false on a runtime without jdk.compiler
Notice that the same program reports a JVM and a runtime everywhere, but the compiler check returns true only on a full JDK. We compare the three parts side by side, follow a source file through the compiler and the JVM, look inside the JVM, and build a small runtime with jlink.
1. JDK, JRE and JVM Compared
The three parts nest inside each other. The JVM loads and executes bytecode, the runtime adds the Java class libraries that every program calls, such as java.lang and java.util, and the JDK adds development tools such as javac, jar and jshell on top of the runtime.

Two short equations sum up the relationship, and the table adds what each part contains and who needs it.
- JRE = JVM + Java class libraries
- JDK = JRE + development tools such as javac, jar, javadoc and jlink
| JVM | JRE (Java runtime) | JDK | |
|---|---|---|---|
| What it is | A virtual machine that executes bytecode | JVM plus the class libraries | Runtime plus development tools |
| Main contents | Class loaders, verifier, interpreter, JIT compiler, garbage collector | java launcher, lib/modules with java.base and other modules | javac, jar, javadoc, javap, jshell, jlink, jpackage, jdb |
| Can it compile source code? | No | No | Yes |
| Can it run a JAR file? | Only as part of a runtime | Yes | Yes |
| Separate download | No, always inside a runtime | Not from Oracle since Java 11; other vendors still publish JRE builds | Yes, from every vendor |
| Who needs it | Every running Java program | Servers and desktops that only run Java apps | Developers and CI build servers |
All three parts are platform-specific, so there is a different JDK for Windows x64, Linux ARM64 or macOS. The bytecode they produce and run is the same on every platform, which is why a JAR built on Windows runs unchanged on a Linux server.
2. From Source File to Running Program
A Java program goes through two tools. The compiler javac, which is part of the JDK, turns a .java source file into a .class file with bytecode, an instruction set for the JVM instead of for a real CPU. The java launcher, which is part of every runtime, starts a JVM that loads the class file and turns the bytecode into machine code for the current CPU.

The following example is a small recipe app with two classes. RecipeApp calls a helper class Units that converts cups of flour to grams, so the JVM must load both classes when the program runs. We compile it with JDK 25 and run it with the same JDK.
javac -d classes src/com/howtodoinjava/units/Units.java src/com/howtodoinjava/recipes/RecipeApp.java
java -cp classes com.howtodoinjava.recipes.RecipeApp
Flour: 300 g
The javac step needs the JDK, whereas the java step works on any Java 25 runtime. Each class file records the class file version it was compiled for, 69 for Java 25, and an older JVM refuses to load it with the unsupported class file major version error. To look at the bytecode itself, we use javap, another JDK tool.
3. Inside the JVM
The Java Virtual Machine Specification describes what a JVM must do, such as the class file format and the meaning of each bytecode instruction, but not how to build one. HotSpot, the JVM in Oracle JDK and in OpenJDK builds such as Eclipse Temurin, Amazon Corretto and Microsoft Build of OpenJDK, is the most widely used implementation. Eclipse OpenJ9 is another JVM that runs the same bytecode.
Every JVM has three parts that work together. A class loader subsystem finds and loads class files, the runtime data areas hold the objects and the call stacks, and the execution engine runs the bytecode and frees memory with the garbage collector.
3.1. Class Loaders and the Three Loading Phases
The JVM loads a class the first time the program uses it, not at startup. Loading happens in three phases.
- Loading finds the class file and reads its bytes into memory.
- Linking verifies the bytecode, so a damaged or hand-edited class file fails with a VerifyError, prepares the static fields with default values and resolves references to other classes.
- Initialization runs the static initializers, so static fields get their real values and static { } blocks execute.
Since Java 9, the JDK uses three built-in class loaders. The bootstrap class loader loads the core classes from java.base, the platform class loader loads other Java SE modules such as java.sql, and the application class loader loads our own classes from the classpath. The following example asks three classes which loader loaded them.
ClassLoader forString = String.class.getClassLoader(); // null (bootstrap class loader)
String forSql = java.sql.Connection.class.getClassLoader().getName(); // "platform"
String forApp = ClassLoader.getSystemClassLoader().getName(); // "app"
The bootstrap loader is part of the JVM itself and has no Java object, so getClassLoader() returns null for String. Each loader first asks its parent, so a class on the classpath named java.lang.String can never replace the real one. When no loader finds a class, the JVM throws ClassNotFoundException or NoClassDefFoundError.
Older articles describe an extension class loader that read rt.jar and jre/lib/ext. Both were removed in Java 9 by JEP 220, and the class libraries live in the lib/modules file of the runtime.
3.2. Runtime Memory Areas
The JVM splits its memory into areas with different lifetimes. Some areas are shared by all threads, and others exist once per thread, which is why local variables are thread-safe and objects on the heap are not.
| Area | Shared or per thread | What it holds | Error when full |
|---|---|---|---|
| Heap | Shared | Every object and array created with new; cleaned by the garbage collector | OutOfMemoryError: Java heap space |
| Metaspace | Shared | Class metadata, method bytecode and the runtime constant pool (replaced PermGen in Java 8) | OutOfMemoryError: Metaspace |
| JVM stack | Per thread | One frame per method call with its local variables and partial results | StackOverflowError |
| PC register | Per thread | The address of the bytecode instruction the thread executes | None |
| Native method stack | Per thread | Frames of native code called through JNI, written in C or C++ | StackOverflowError |
We set the heap size with -Xms and -Xmx, and the stack size per thread with -Xss. The guides to OutOfMemoryError and StackOverflowError show how each area fails in practice.
3.3. Interpreter and JIT Compiler
The execution engine runs bytecode in two ways. The interpreter executes one bytecode instruction at a time, so it starts fast but runs slowly. The JIT compiler, which compiles bytecode while the program runs, watches which methods run often, compiles these hot methods to optimized machine code and replaces the interpreted version.
HotSpot uses two JIT compilers by default, a fast C1 compiler for code that warms up and an optimizing C2 compiler for the hottest code. Running interpreted and compiled code side by side is the “mixed mode” that java -version prints. With -Xint, the JVM uses only the interpreter, which helps when we suspect a JIT bug but makes most programs much slower.
java -version
java -Xint -version
openjdk version "25.0.4.1" 2026-08-18 LTS
OpenJDK Runtime Environment Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS)
OpenJDK 64-Bit Server VM Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS, mixed mode, sharing)
OpenJDK 64-Bit Server VM Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS, interpreted mode, sharing)
The JIT compiler explains why a Java service gets faster during its first minutes after a deploy, a phase called warm-up. For example, an order service handles its first requests in interpreted mode, and after a few thousand calls the hot methods run as compiled machine code. The garbage collection algorithms guide covers the other job of the execution engine.
Older JDKs had a client VM and a server VM, chosen with -client or -server. A 64-bit JDK has only the server VM and ignores -client, and since JDK 25 no 32-bit x86 build is left, so the choice no longer exists.
4. The Java Runtime After Java 11
Up to Java 8, Oracle offered a JRE download for end users, and Java desktop apps and applets expected one on the machine. JDK 11 dropped the JRE and Server JRE together with applets and Java Web Start, and recommended jlink to build smaller runtimes. The term JRE still describes the runtime part of the JDK, and several vendors, such as Eclipse Temurin, still publish JRE builds.
The jlink tool copies only the modules an app needs into a new runtime folder. Such a runtime has a java launcher and the JVM, but no javac. The following example builds a runtime with only java.base, which is enough for the recipe app, and runs the app with it.
jlink --add-modules java.base --strip-debug --no-man-pages --no-header-files --output recipe-runtime
recipe-runtime/bin/java -cp classes com.howtodoinjava.recipes.RecipeApp
Flour: 300 g
On Linux x64, the recipe-runtime folder takes 56 MB, compared with 302 MB for the full Temurin JDK 25. To find the modules an app needs, we run jdeps –print-module-deps on its JAR files. A desktop app goes one step further with jpackage, which bundles such a runtime into a native installer.
A team that deploys a Spring Boot service in Docker profits most from a small runtime. The build stage of the Dockerfile uses a JDK image to compile and package the JAR, and the final stage copies only the JAR and a JRE or jlink runtime, so the production image is smaller and contains no compiler.
5. Tools That Come With the JDK
The JDK adds about 30 command-line tools in its bin folder. Most developers use a handful of them every day, and a few others when they debug a running app.
| Tool | What we use it for |
|---|---|
| javac | Compile .java source files into .class files |
| java | Start the JVM with a class, a JAR or a single source file |
| jar | Create and extract JAR files |
| javadoc | Generate HTML API documentation from source comments |
| javap | Disassemble class files and show bytecode |
| jshell | Try Java statements interactively |
| jlink, jpackage | Build custom runtimes and native installers |
| jdeps | List the packages and modules a JAR depends on |
| jdb | Debug from the command line |
| jcmd, jstack, jfr, jconsole | Inspect a running JVM, take thread dumps and record flight data |
| keytool, jarsigner | Manage keystores and sign JAR files |
Several tools in older lists are gone. The appletviewer, javaws and xjc tools were removed in Java 11, jhat and extcheck in Java 9, policytool in Java 10, and apt already in Java 8. The JShell tutorial is a good place to start with the interactive tool.
6. Which One Should We Install?
We never install a JVM on its own, so the choice is between a full JDK and a runtime. The answer depends on whether the machine compiles code or only runs it.
| Machine | Install | Reason |
|---|---|---|
| Developer laptop | JDK | The IDE and Maven or Gradle call javac |
| CI build server | JDK | The build compiles, tests and packages the app |
| Production server or container | JRE build or jlink runtime | Smaller, fewer tools to patch, no compiler |
| User desktop with a Java app | Runtime bundled by jpackage | The installer brings its own runtime, so users install nothing else |
| Learning Java | JDK | Compiling and running from the terminal needs both tools |
For a first setup, the JDK is the right download, because it runs programs too. The steps for Windows are in the guide to install Java on 64-bit Windows, and the Java tutorial continues from there with the language itself.
7. JDK, JRE and JVM FAQs
Old JDK versions had a separate jre folder inside the JDK, and much of the confusion about the three terms dates from that layout.
7.1. Does the JDK include the JRE?
Yes. Every JDK contains a complete Java runtime, so anything a JRE runs also runs on the JDK. Since Java 9, the JDK no longer has a separate jre subfolder, and the runtime files sit in its bin and lib folders.
7.2. Is the JVM platform independent?
No. The JVM is a native program built for one operating system and CPU, such as Windows x64 or Linux ARM64. The bytecode is platform independent, and each platform has its own JVM that runs that bytecode.
7.3. Can we run a Java program with only the JVM?
No. A program always needs the class libraries too, because even String and System come from java.base. The smallest unit we can run is a runtime, such as one built with jlink from java.base alone.
7.4. How many class loaders are there in Java?
There are three built-in class loaders since Java 9. These are the bootstrap, platform and application class loaders, and apps and frameworks such as application servers can create their own custom loaders on top of them.
7.5. Does the JVM interpret or compile bytecode?
Both. The JVM starts by interpreting the bytecode that javac produced ahead of time, and its JIT compiler turns the hot methods into machine code while the program runs, as shown in section 3.3.
7.6. Do we need the JDK to run a JAR file?
No. Any Java runtime of the same or a newer feature version runs a JAR with java -jar app.jar. The JDK is needed only to compile the code or to use tools such as jlink and jcmd.
8. Conclusion
The JVM executes bytecode, the Java runtime adds the class libraries the code calls, and the JDK adds the compiler and the other tools on top. A developer machine and a build server need the JDK, whereas production only needs a runtime.
Since Java 11, Oracle ships only the JDK, and we either use a vendor JRE build or create a small runtime with jlink. Inside the JVM, the class loaders, the memory areas and the interpreter with its JIT compiler explain most of what we see at runtime, from ClassNotFoundException to slow first requests after a deploy.
9. References
- The Java Virtual Machine Specification, Java SE 25
- The jlink Command (Java 25)
- ClassLoader (Java 25 API)
- JEP 220, Modular Run-Time Images
- JEP 122, Remove the Permanent Generation
- JDK 11 Release Notes
Happy Learning !!