Difference Between JDK, JRE and JVM in Java (Java 25)

Learn the difference between JDK, JRE and JVM in Java 25, what each one contains, how class loaders and the JIT work and why Oracle ships no JRE.

Nested boxes showing the JDK containing development tools and a Java runtime, and the runtime containing the class libraries and the JVM with class loaders, memory areas and execution engine

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.

Nested boxes showing the JDK containing development tools and a Java runtime, and the runtime containing the class libraries and the JVM with class loaders, memory areas and execution engine
The JDK contains a Java runtime, and the runtime contains the JVM that executes bytecode

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
JVMJRE (Java runtime)JDK
What it isA virtual machine that executes bytecodeJVM plus the class librariesRuntime plus development tools
Main contentsClass loaders, verifier, interpreter, JIT compiler, garbage collectorjava launcher, lib/modules with java.base and other modulesjavac, jar, javadoc, javap, jshell, jlink, jpackage, jdb
Can it compile source code?NoNoYes
Can it run a JAR file?Only as part of a runtimeYesYes
Separate downloadNo, always inside a runtimeNot from Oracle since Java 11; other vendors still publish JRE buildsYes, from every vendor
Who needs itEvery running Java programServers and desktops that only run Java appsDevelopers 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.

javac compiles RecipeApp.java into RecipeApp.class bytecode, and the JVM loads it and produces machine code for Windows x64 or Linux ARM64
We compile once with the JDK, and any Java runtime runs the same bytecode

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.

AreaShared or per threadWhat it holdsError when full
HeapSharedEvery object and array created with new; cleaned by the garbage collectorOutOfMemoryError: Java heap space
MetaspaceSharedClass metadata, method bytecode and the runtime constant pool (replaced PermGen in Java 8)OutOfMemoryError: Metaspace
JVM stackPer threadOne frame per method call with its local variables and partial resultsStackOverflowError
PC registerPer threadThe address of the bytecode instruction the thread executesNone
Native method stackPer threadFrames 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.

ToolWhat we use it for
javacCompile .java source files into .class files
javaStart the JVM with a class, a JAR or a single source file
jarCreate and extract JAR files
javadocGenerate HTML API documentation from source comments
javapDisassemble class files and show bytecode
jshellTry Java statements interactively
jlink, jpackageBuild custom runtimes and native installers
jdepsList the packages and modules a JAR depends on
jdbDebug from the command line
jcmd, jstack, jfr, jconsoleInspect a running JVM, take thread dumps and record flight data
keytool, jarsignerManage 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.

MachineInstallReason
Developer laptopJDKThe IDE and Maven or Gradle call javac
CI build serverJDKThe build compiles, tests and packages the app
Production server or containerJRE build or jlink runtimeSmaller, fewer tools to patch, no compiler
User desktop with a Java appRuntime bundled by jpackageThe installer brings its own runtime, so users install nothing else
Learning JavaJDKCompiling 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

Happy Learning !!

Source Code on Github

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.