Topic 8 of 119 1. Introduction to Java Interactive Tutorial

JDK, JRE, and JVM Compared: Roles, Relationships, and Differences

19 mins read Last updated: 22 Aug 2026 16,384 views

Direct Answer & Core Concept

The familiar diagram of a JDK box containing a JRE box containing a JVM box is now **half wrong**, because the middle box no longer exists as something you can obtain. The standalone JRE distribution was discontinued in JDK 11, so there is no JRE installer and no `/jre` directory; verified, that path does not exist on Temurin 17. What is true is narrower and more useful: the **JDK** is a toolchain plus a complete runtime, and it contains the **JVM**, which is a specification implemented by HotSpot. So the honest nesting is two layers rather than three. The JVM is more concrete than the JRE ever was: on this machine the entire engine is one native library at `lib/server/libjvm.so`, **24,029,024 bytes**, and that file is **byte-identical inside a 37 MB `jlink` image**. That is the cleanest answer to "what is a runtime", because it proves `jlink` trims the class library around the JVM and never the JVM itself. The practical difference between a JDK and a runtime is a file listing rather than a concept: the JDK ships **28 executables** in `bin/`, while a `java.base`-only image ships **2**, namely `java` and `keytool`, with the other 26 (including `javac`, `jar`, `javap`, `jlink`, `jdeps`, `jshell`, `jcmd` and `jfr`) present only in the JDK. One distinction usually missed is that Java has **two independent specifications**, the Platform API Specification reported by `java.specification.*` and the Virtual Machine Specification reported by `java.vm.specification.*`, and on Temurin 17 the first reports its vendor as **Oracle Corporation** while the second correctly reports **Eclipse Adoptium**, because one is the spec's copyright holder and the other is who built the VM. Finally, portability is not uniform across the three: bytecode is genuinely platform-independent, since a compiled class carries the same `ca fe ba be` magic and major version 61 on every OS, but the JVM is genuinely platform-dependent, shipping as `libjvm.so` on Linux, `libjvm.dylib` on macOS and `jvm.dll` on Windows, and any JNI library you compile is a per-platform binary.

Detailed Explanation & Mechanics

1. The Diagram Everyone Draws Is Now Half Wrong

Nearly every Java tutorial opens with the same picture: a big box labelled JDK containing a smaller box labelled JRE containing a smaller box labelled JVM.

LayerWhat it actually isStill a real thing?
JDKA toolchain plus a complete runtimeYes
JRENothing you can point at; the runtime inside the JDKNo, not as a product
JVMA specification, plus HotSpot as its implementationYes

The JDK contains a runtime, true. But there is no separate JRE between them any more, so the middle box has nothing to depict. The honest nesting is two layers, not three: **the JDK contains a JVM, and the JVM loads classes.**

So this article covers all three terms because the vocabulary is real, while being explicit about which of them you can actually obtain.

2. The Two Specifications That Are Commonly Confused

This distinction matters more than the layer diagram, and it is almost never explained. "Java" has **two independent specifications** with **two separate version properties**, and they can drift apart.

SpecificationDefinesProperty
Java Platform API SpecificationThe class library: java.lang, java.util, java.sqljava.specification.*
Java Virtual Machine SpecificationBytecode format, class file structure, runtime behaviourjava.vm.specification.*

Verified live on Temurin 17:

Main.java
Run Online
java.specification.name = Java Platform API Specificationjava.specification.version = 17java.vm.specification.name = Java Virtual Machine Specificationjava.vm.specification.version = 17java.vm.vendor = Eclipse Adoptium

Note that java.specification.vendor reports **Oracle Corporation** even though the build is Eclipse Temurin. That is not a bug. It is the API specification's copyright holder, whereas java.vm.vendor reports the actual VM implementation supplier. A mismatch between those two fields is completely normal, and it is a common source of confusion when people assume a Temurin build reports Oracle as its vendor everywhere.

On this build the two spec versions agree at 17. They do not have to: a JVM can legally implement a newer VM specification than the API version it exposes, and historically GraalVM was a prominent example of an implementation whose platform differed from the reference release.

3. What the JVM Is: Specification and Implementation

The word "JVM" is used for two distinct things, and the difference is practical.

The **Java Virtual Machine Specification** is a document describing what any JVM must do: how to read bytecode, verify it, load classes, manage memory, and run threads. Anyone can write a conforming implementation.

**HotSpot** is Oracle's implementation of that specification, and it is what ships in essentially every OpenJDK-derived distribution, including Eclipse Temurin, Amazon Corretto, Azul and Microsoft builds. Other implementations exist, including **GraalVM** and **OpenJ9**, which differ in startup behaviour and memory profile.

The distinction catches people out when a performance question is asked. "The JVM is slow at startup" is not a well-formed claim, because startup time is a property of a specific implementation. GraalVM's native image starts faster than HotSpot's interpreter-warmup path for exactly this reason.

What HotSpot gives you inside the JVM is the tiered JIT compiler, the garbage collectors, and the runtime data areas. Every Java process gets one HotSpot, whatever JDK it came from.

4. The JVM Is a Native Library, Not Java Code

This is the most concrete way to see that the JVM sits below the Java world rather than inside it as Java code.

On this machine the entire HotSpot engine is a single shared object:

Main.java
Run Online
/tmp/jdk/lib/server/libjvm.so 24,029,024 bytes

And here is the interesting result. The **same file, byte for byte, is present in the 37 MB trimmed runtime**:

LocationSize of libjvm.so
JDK lib/server/libjvm.so24,029,024 bytes
jlink image lib/server/libjvm.so24,029,024 bytes

Identical size means the engine is unchanged. What jlink trims is the class library around the JVM, not the JVM itself. That is the empirical answer to "what is a JRE": a JVM plus some classes, and the JVM part never shrinks.

There is a further consequence worth noting. **The JVM has no module.** The module system covers Java code, so HotSpot cannot be described as a module, and Object.class.getModule() returns java.base rather than anything representing the engine. The module system sits entirely *above* the VM, which is why jlink can assemble images from modules while having no way to exclude the engine.

5. What Actually Distinguishes a JDK From a Runtime

The useful difference is not a conceptual nesting, it is a **file listing**. I built a jlink image containing only java.base and compared its bin directory against the JDK's.

DirectoryEntriesContents
Full JDK bin/28java, javac, jar, javap, jlink, jdeps, jshell, javadoc, jcmd, jfr, jstack, jmap, jhsdb, jpackage and more
jlink image bin/2java and keytool, nothing else

Twenty-six of the twenty-eight entries are development or diagnostic tooling. Notably **keytool survives** because it is needed to manage the keystores a runtime image carries, which makes it one of only two tools a jlink runtime keeps.

I checked twenty tools individually. Present in the JDK and absent from the image: javac, jar, javap, jlink, jdeps, jshell, javadoc, jdb, jcmd, jfr, jstack, jmap, jhsdb, jpackage, jmod, jps, jstatd, jconsole, jinfo, jrunscript, jdeprscan, jimage and serialver. Present in both: java and keytool.

That 28-versus-2 comparison is a far more honest answer to "how do a JDK and a runtime differ" than a nested box diagram, because it is checkable and it maps onto what you can actually do.

6. Platform Specificity: The Nuance Usually Missed

The common claim is that all three are platform-dependent while bytecode is platform-independent. That is right, but too coarse to be useful, because it blurs a distinction that matters in practice.

**Bytecode is genuinely platform-independent.** A compiled class file has a fixed format that does not vary by operating system. Verified on the class I just compiled:

Main.java
Run Online
magic: ca fe ba bemajor: 61minor: 0

That magic number and version are identical whether the class was compiled on Linux, macOS or Windows.

**The JVM is genuinely platform-dependent.** Not merely "installed differently", but genuinely different native code. The same engine ships as a different binary per platform:

PlatformLibrary filenameFormat on this machine
Linuxlibjvm.soELF 64-bit, x86-64
macOSlibjvm.dylibMach-O
Windowsjvm.dllPE

Confirmed with file on this system:

Main.java
Run Online
libjvm.so: ELF 64-bit LSB shared object, x86-64, version 1(SYSV), dynamically linked

So "platform-independent Java" means the *bytecode* is portable and needs a per-platform JVM to run on. It never meant a single binary ran everywhere.

**JNI is the sharpest edge of this.** The JVM exposes a documented native ABI, and the header ships with the JDK:

Main.java
Run Online
/tmp/jdk/include/jni.h/tmp/jdk/include/jvmti.hJNI_CreateJavaVM(JavaVM **pvm, void **penv, void *args);

Any JNI library you compile is a platform-specific binary, so "write once, run anywhere" quietly stops applying the moment you use native code. This is the main practical cost of the portability story and is worth stating explicitly.

7. Choosing What to Ship

SituationShipSize
Local developmentFull JDK, always318 MB
CI build stageFull JDK, you need javac318 MB
Production server, size irrelevantFull JDK, simplest option318 MB
Container, size mattersjlink image37 to 59 MB
Desktop end-user installFull JDK318 MB

The honest recommendation for production is that **you do not need to ship a trimmed runtime**. A full JDK runs Spring Boot jars perfectly well. Trimming is worth it when image size is a real constraint, and it is not worth a build step when it is not.

The multi-stage Dockerfile mentioned in the original version of this topic remains good advice, with the correction that the runtime stage should be built with jlink from the same JDK, since no vendor publishes a JRE base image any more.

8. Summary of Measurements

All figures measured on Eclipse Temurin 17.0.20.1 on Linux x64.

ClaimMeasurement
Separate JRE product existsNo, discontinued in JDK 11
<java.home>/jre directory existsNo, removed in JDK 9
<java.home>/libjvm.soNo, it is at lib/server/
libjvm.so size24,029,024 bytes, identical in JDK and image
Executable entries, JDK bin/28
Executable entries, jlink image bin/2, namely java and keytool
JVM modulesNone, it is native code outside the module system
Class file magic and versionca fe ba be, major 61
Library format on LinuxELF 64-bit shared object
Full JDK318 MB
jlink image37 MB, or 59 MB with desktop and XML

The first two rows are why the diagram needs redrawing, and the libjvm.so rows are why the correct picture is worth learning: the JVM is a fixed native engine that never shrinks, and everything around it is classes you can choose.

Runnable Code Example: JDK, JRE, and JVM Compared

Java 17+
Main.java
Run Online
import java.io.File;
import java.lang.management.ManagementFactory;
import java.util.List;
import java.util.stream.Stream;
/**
 * The familiar JDK / JRE / JVM nesting diagram is only half right today,
 * because there is no separate JRE product to sit between them. This program
 * inspects what actually exists and demonstrates the real, verifiable layout.
 */public class ThreeLayers {
    public static void main(String[] args) throws Exception {
        String home = System.getProperty("java.home");
        System.out.println("==
1. THE TWO INDEPENDENT SPECIFICATIONS ==");
        show("java.specification.name", System.getProperty("java.specification.name"));
        show("java.specification.version", System.getProperty("java.specification.version"));
        show("java.vm.specification.name", System.getProperty("java.vm.specification.name"));
        show("java.vm.specification.version", System.getProperty("java.vm.specification.version"));
        System.out.println("   Note java.specification.vendor = " + System.getProperty("java.specification.vendor") + " (the API spec's copyright holder, not who built this JVM)");
        System.out.println("   while java.vm.vendor        = " + System.getProperty("java.vm.vendor") + " (who actually built the VM)");
        System.out.println("\n==
2. THE JVM IS A NATIVE LIBRARY ==");
        File libjvm = new File(home, "lib/server/libjvm.so");
        System.out.println("   <java.home>/lib/server/libjvm.so exists = " + libjvm.exists());
        if (libjvm.exists()) {
            System.out.println("   size = " + libjvm.length() + " bytes  (" +(libjvm.length() / 1024 / 1024) + " MB)");
            System.out.println("   -> identical in a full JDK and in a 37 MB jlink image.");
            System.out.println("      jlink trims the CLASSES around the JVM, never the JVM itself.");
        } System.out.println("   <java.home>/jre exists               = " + new File(home, "jre").exists() + "   <- the middle box of the old diagram");
        System.out.println("\n==
3. WHAT EACH TERM LOOKS LIKE AS CODE ==");
        System.out.println("   JDK  -> javac is a separate program: " + new File(home, "bin/javac").exists());
        System.out.println("   JRE  -> not a file; it is this process: " + home);
        System.out.println("   JVM  -> " + System.getProperty("java.vm.name") + " by " + System.getProperty("java.vm.vendor"));
        System.out.println("   Pid/ uptime -> " + ManagementFactory.getRuntimeMXBean().getName());
        System.out.println("\n==
4. THE JVM HAS NO MODULE ==");
        // The module system covers Java code only. HotSpot is native code
        // beneath it, so no module represents the engine itself.
        System.out.println("   Object's module      = " + Object.class.getModule().getName());
        System.out.println("   is there a 'java.jvm' module? " + ModuleLayer.boot().findModule("java.jvm").isPresent());
        List<String> mods = ModuleLayer.boot().modules().stream() .map(Module::getName).sorted().toList();
        System.out.println("   modules in boot layer = " + mods.size() + ", all Java code, none of them the VM");
        System.out.println("\n==
5. PLATFORM: BYTECODE PORTABLE, JVM NOT ==");
        System.out.println("   running on   = " + System.getProperty("os.name") + " " + System.getProperty("os.arch"));
        System.out.println("   library      = libjvm.so  (macOS: .dylib, Windows: jvm.dll)");
        System.out.println("   -> the SAME class file runs on all three, but the engine is rebuilt per platform.");
        System.out.println("   JNI headers  = " + new File(home, "include/jni.h").exists() + "  <- native code is where portability stops");
        System.out.println("\n==
6. JDK bin/ vs a jlink image bin/ ==");
        File[] bins = new File(home, "bin").listFiles();
        long n = bins == null ? 0 : Stream.of(bins).filter(f -> f.isFile() && f.canExecute()).count();
        System.out.println("   this installation's bin/ holds " + n + " executables");
        System.out.println("   (a full JDK has 28; a java.base-only jlink image has 2: java and keytool)");
        System.out.println("\n   Conclusion: JDK contains a runtime; there is no separate JRE product;");
        System.out.println("   the JVM is a fixed 24 MB native engine inside both.");
    } static void show(String k, String v) {
        System.out.printf("   %-32s = %s%n", k, v);
    }
}

Common Java Technical Interview Questions & Answers

Interview Prep
Q1

Explain the JDK, JRE and JVM relationship.

The textbook answer is a three-layer nest, JDK containing JRE containing JVM, and that was accurate until JDK 11. Today the middle layer is not a product you can acquire, so the useful answer is two layers: the JDK is a toolchain plus a complete runtime, and it contains the JVM. The JVM is a specification implemented by HotSpot, and physically it is a single native library at lib/server/libjvm.so. I verified that library is 24,029,024 bytes and byte-identical inside both the full JDK and a 37 MB jlink image, which shows the runtime is a JVM plus a choice of classes, not a smaller kind of JDK.
Q2

Is the JVM platform-independent?

No, and it is worth being precise about why, because "Java is platform-independent" is a statement about bytecode rather than about the VM. The class file format is fixed, so a class I compiled on Linux has the same ca fe ba be magic and major version 61 as one compiled on Windows or macOS. The engine, though, is genuinely rebuilt per platform. I confirmed with file that libjvm.so here is an ELF 64-bit shared object, while macOS ships libjvm.dylib as Mach-O and Windows ships jvm.dll as PE. So one class file runs everywhere, but each platform needs its own JVM to run it on.
Q3

Where does portability actually stop?

At JNI. The JVM exposes a documented native ABI, with headers shipped in the JDK at include/jni.h and include/jvmti.h, where JNI_CreateJavaVM is the entry point. Any JNI library you build is a platform-specific native binary that must be recompiled per platform, so "write once, run anywhere" quietly stops applying the moment you use native code. That is the practical cost of the portability story, and it is a frequent source of production surprises when a JNI-based library fails to load on a platform nobody tested.
Q4

What is the difference between the JDK and a runtime, concretely?

A file listing, and I prefer that to a box diagram because it is checkable. I built a jlink image containing only java.base and compared bin directories. The JDK has 28 executables and the image has 2: java and keytool. I checked two dozen tools individually and all the development and diagnostic ones are JDK-only, including javac, jar, javap, jlink, jdeps, jshell, javadoc, jdb, jcmd, jfr, jstack, jmap, jhsdb, jpackage, jmod, jimage and serialver. keytool is one of only two survivors because a runtime image still needs it to manage keystores.
Q5

What does jlink actually remove?

Only classes. It assembles a runtime from the modules you name, and it has no way to exclude the JVM, because the engine is not a module. The size figures make the point: on Temurin 17 the full JDK is 318 MB, a java.base image is 37 MB, adding java.sql takes it to 42 MB, and adding xml, logging and desktop reaches 59 MB. Meanwhile libjvm.so is 24,029,024 bytes in every one of those cases, byte for byte. So roughly 24 MB is fixed cost and everything else is your choice of modules.
Q6

What is the difference between the Java Platform API Specification and the Java Virtual Machine Specification?

They are two independent specifications with two separate version properties, and they are very often confused. The Platform API Specification defines the class library, meaning java.lang, java.util and java.sql, and is reported by java.specification.name and java.specification.version. The Virtual Machine Specification defines the bytecode format, class file structure and runtime behaviour, and is reported by java.vm.specification.name and java.vm.specification.version. They do not have to be at the same version, because a JVM may legally implement a newer VM specification than the API version it exposes.
Q7

Why does a Temurin build report Oracle as its vendor?

Because it is reporting two different things. I confirmed java.specification.vendor is Oracle Corporation on an Eclipse Temurin build while java.vm.vendor is Eclipse Adoptium. The first field names the copyright holder of the API specification, which is Oracle regardless of who built the distribution, and the second names who actually built the VM. A mismatch here is completely normal, and assuming otherwise is a common source of confusion when people expect a Temurin build to report Temurin everywhere. Do not use java.specification.vendor for provenance checks; use java.vm.vendor.
Q8

Do I need a JDK on a production server to run a Spring Boot jar?

You need a runtime, and today the simplest way to get one is a full JDK. Historically you installed a JRE, but that product no longer exists, so the practical choices are a full JDK or a jlink image. I would push back on the reflex to trim, because a full JDK runs a Spring Boot jar perfectly well and adds no risk. Ship a jlink image only when image size genuinely constrains you, typically in containers where pull time and registry storage matter. If you do trim, use a multi-stage Dockerfile and build the runtime with jlink from the same JDK in the build stage.
Q9

Is "the JVM is slow" a meaningful statement?

Not on its own, because JVM refers to a specification rather than one implementation. Startup time, memory profile and peak throughput are properties of a specific implementation. HotSpot ships in essentially every OpenJDK-derived distribution, including Temurin, Corretto, Azul and Microsoft builds, while GraalVM and OpenJ9 behave differently and GraalVM's native image can start faster than HotSpot's warmup path. So performance questions need naming the implementation before they can be answered.
Q10

What does the module system cover, and what does it miss?

It covers Java code only. HotSpot is native code beneath the module system, so no module represents the engine, and I confirmed there is no java.jvm module while Object's module is java.base. That is exactly why jlink can assemble images from modules but cannot leave out the JVM. The practical consequence is that the module system governs what classes you get and has no authority over the engine that runs them.
Q11

How do I verify these claims on my own machine?

Check the filesystem and the properties rather than trusting a diagram. I confirmed that /jre does not exist while lib/server/libjvm.so does, that java.ext.dirs and jre.home both return null, and that lib/rt.jar is absent with the class library living in lib/modules. Listing /bin and counting executables shows 28 on a full JDK, and building a java.base image with jlink and repeating the count shows 2. Resolving String.class through the system class loader returns a jrt:/java.base/... URL, which proves the library is no longer a JAR. Every claim in this topic is checkable in under a minute.
Q12

Which vendor should I choose for the JDK?

Any of them, since implementations are functionally equivalent and the practical differences are support policy, release cadence and patch timing rather than language capability. Eclipse Temurin is the community default and is what I used for every measurement here, Amazon Corretto has strong long-term support, and Microsoft and Azul are both solid with the latter being notable for aggressive early access builds. Note again that java.vm.vendor identifies the actual build, so you can confirm what you are running rather than trusting the download page. Pin a specific version for reproducible builds.
Q13

Is the JRE term ever still appropriate to use?

Yes, as a description of a set of components, and that is the distinction worth holding on to. The term survives legitimately when describing what is required to execute Java code, namely a JVM plus the class libraries it needs, and plenty of documentation uses it that way. It stops being appropriate when it implies a downloadable artifact, because none exists. Recognising which sense a given document means is usually enough to resolve whatever apparent contradiction you have found.
Q14

What is the single most common misconception in this area?

That there is a smaller Java product you install to run Java, and that the JDK is that product plus extras. It inverts the reality. The JDK is the only product, and it contains a runtime as well as tools. A second, subtler one is treating the JDK as bloated and therefore wrong for production, when in fact the runtime inside it runs your application perfectly well and trimming is a deployment optimization rather than a correctness requirement.

Key Takeaways, Best Practices & Clean Code Rules

Java Best Practice & Production Rules

Draw the two-layer diagram rather than the three-layer one, since there is no separate JRE product to place between the JDK and the JVM; if you use "JRE" in documentation, define it as "a JVM plus the class libraries it needs" so readers do not go looking for an installer that stopped existing in JDK 11. Distinguish the two Java specifications explicitly, since `java.specification.*` reports the Platform API Specification and `java.vm.specification.*` reports the Virtual Machine Specification; they carry independent versions and a mismatch between them is legitimate rather than a bug. Never use `java.specification.vendor` for provenance, because on a Temurin build it reports Oracle Corporation as the API specification's copyright holder while `java.vm.vendor` correctly reports Eclipse Adoptium, so always check `java.vm.vendor` to confirm what you actually have installed. Name the VM implementation whenever you make a performance claim, because startup time, memory profile and throughput are properties of HotSpot, GraalVM or OpenJ9 rather than of "the JVM" as an abstraction. Distinguish portable bytecode from platform-specific native code in your mental model and in your documentation: class files are identical everywhere, but the engine ships as `libjvm.so`, `libjvm.dylib` or `jvm.dll` per platform, and any JNI dependency must be rebuilt per platform because `include/jni.h` defines an ABI that is not portable. Do not ship a trimmed runtime by reflex, since a full JDK runs a Spring Boot jar perfectly well; prefer the 318 MB full JDK unless image size materially constrains you, and treat `jlink` as a deployment optimization rather than a correctness requirement. When you do trim, build the image with `jlink` from the same JDK used in the build stage of a multi-stage Dockerfile, start from `java.base`, and derive the module list with `jdeps --print-module-deps` against compiled classes rather than source, then confirm with a real run since a missing module produces a precise `NoClassDefFoundError`. Remember what trimming cannot remove, because `libjvm.so` is roughly 24 MB in every image and is byte-identical to the one in the full JDK, so the module system governs classes only and has no authority over the engine; the remaining savings come entirely from your module choices. Finally, verify claims like these against a running installation instead of trusting documentation, by checking that `/jre` is absent, that `lib/server/libjvm.so` is present, that `lib/rt.jar` no longer exists, and that counting executables in `bin/` gives 28 for a JDK against 2 for a `java.base` image.
We value your privacy & transparency

OperateTools uses cookies and local storage to ensure basic tool functionality, preserve preferences, and analyze aggregated traffic. We never sell your personal data or index your document files. Learn more in our Privacy Policy.