JDK, JRE, and JVM
Clarify the distinction between JDK, JRE, and JVM — the three pillars of Java development and runtime environments.
JDK, JRE, and JVM
New Java developers often confuse JDK, JRE, and JVM. These three components form a layered architecture — each building on the previous. Understanding their boundaries helps you install the right package, debug runtime issues, and explain Java’s architecture in interviews.
When to Use
| Component | Use When |
|---|---|
| JDK | You are writing, compiling, or building Java code |
| JRE | You are only running Java applications (no compilation) |
| JVM | You need to understand execution internals or tune runtime behavior |
The Three Layers
Think of Java as a three-story building:
flowchart TB
subgraph JDK["JDK (Java Development Kit)"]
direction TB
COMP[javac Compiler]
LIBS[Tools + Libraries]
JRE_L[JRE Included]
end
subgraph JRE["JRE (Java Runtime Environment)"]
direction TB
JVM_R[JVM]
RT[JDK Library Classes]
end
subgraph JVM["JVM (Java Virtual Machine)"]
direction TB
EXEC[Execution Engine]
MEM[Memory Management]
end
JDK --> JRE --> JVM
- JVM — The engine that executes bytecode
- JRE — JVM plus the class library needed to run Java applications
- JDK — JRE plus development tools (compiler, debugger, profilers)
JDK (Java Development Kit)
The JDK is a full-featured development environment. It includes everything needed to develop, compile, and debug Java applications.
What’s Inside the JDK
| Tool | Purpose |
|---|---|
javac | Java compiler — transforms .java source files to .class bytecode |
java | Launcher — starts a JVM and runs your application |
javap | Disassembler — inspects compiled .class files |
javadoc | Documentation generator — builds HTML API docs from annotations |
jar | Archiver — packages .class files and resources into .jar files |
jdb | Debugger — command-line debugging interface |
jdeps | Dependency analyzer — lists package-level dependencies |
jhsdb | HotSpot debugger — inspects running JVM internals |
JDK Editions
| Edition | Target Platform |
|---|---|
| JDK SE (Standard Edition) | Desktop, server, general-purpose applications |
| JDK EE (Enterprise Edition) | Deprecated — Jakarta EE is the successor |
| JDK ME (Micro Edition) | Mobile devices, embedded systems, IoT |
| OpenJDK | Open-source reference implementation (LGPL licensed) |
JRE (Java Runtime Environment)
The JRE provides the runtime environment for end users who only need to run Java applications — not develop them. It includes the JVM and the core class library (java.lang, java.util, java.io, etc.).
What You Get with JRE
- JVM — The bytecode executor
- Core class library — rt.jar containing java.and javax. packages
- Native libraries — Platform-specific .dll / .so files for native operations
- Configuration files — java.policy for security permissions
JRE vs No-JRE
If you try to run java MyApp.class without a JRE installed, you get:
$ java MyApp
bash: java: command not found
Installing just the JRE (smaller download) lets users run applications but not compile them.
JVM (Java Virtual Machine)
The JVM is the abstract specification that defines how bytecode becomes machine instructions. Different vendors implement the JVM (HotSpot, OpenJ9, GraalVM, Azul Zing).
JVM Implementation Variants
| Implementation | Vendor | Key Feature |
|---|---|---|
| HotSpot | Oracle, Eclipse Temurin | Tiered compilation, mature GC algorithms |
| OpenJ9 | Eclipse | Fast startup, low memory footprint |
| GraalVM | Oracle | Native image compilation, polyglot (JS, Python, Ruby) |
| Azul Zing | Azul | ReadyNow! for predictable latency, pauseless GC |
The JVM Specification vs Implementation
The Java Language Specification defines the language; the JVM Specification defines the runtime. The specification covers:
- Class file format (bytecode instructions)
- Runtime memory model
- Thread synchronization primitives
- Instruction set architecture (abstract, not x86-specific)
Implementations vary in performance characteristics, GC algorithms, and JIT optimization strategies — but all produce identical behavior for the same bytecode.
Production Failure Scenarios + Mitigations
Wrong JVM Installed for Your JDK Version
Scenario: Installing a JDK 17 but running an older JRE 11 that is incompatible with your application.
Symptoms:
UnsupportedClassVersionErrorat runtime- Application crashes on missing methods that exist in newer JDK
Mitigation:
# Check installed Java version
java -version
javac -version
# Set JAVA_HOME to correct JDK path
export JAVA_HOME=/usr/lib/jvm/temurin-17-jdk
export PATH=$JAVA_HOME/bin:$PATH
# In Maven/Gradle projects, enforce JVM version
# maven-compiler-plugin:
# <source>17</source>
# <target>17</target>
JRE Missing Core Library Classes
Scenario: Application deployed to minimal JRE image missing EE libraries (e.g., JAXB, JAX-WS).
Mitigation:
# Verify all dependencies are on classpath
java -cp "lib/*:app.jar" com.example.Main
# Use jdeps to check dependency graph
jdeps --print-deps myapp.jar
# Package full JDK for deployment if dependencies are uncertain
# Or use jlink to create custom runtime image with required modules
jlink --add-modules java.base,java.logging,com.example.mymodule --output myruntime
JVM Vendor Lock-In
Scenario: Application assumes HotSpot-specific flags that fail on OpenJ9.
Mitigation:
# Test with target JVM before deployment
# Avoid vendor-specific flags like:
# -XX:+UseStringDeduplication # HotSpot only
# -XX:+UseJVMCICompiler # HotSpot only
# Stick to standard flags documented across all JVMs:
# -Xmx, -Xms, -XX:+UseG1GC, -XX:MaxGCPauseMillis=200
Trade-off Table
| Decision | JDK Advantage | JDK Disadvantage |
|---|---|---|
| Full JDK vs JRE only | Can compile, debug, and diagnose locally | Larger disk and memory footprint |
| HotSpot vs OpenJ9 | Most tested, widest GC options | Higher memory usage |
| OpenJDK vs Oracle JDK | Free, open-source, no license concerns | Oracle JDK includes additional commercial tools |
| GraalVM native image | Fast startup, small container images | Long build times, limited reflection support |
Implementation Snippet: Verifying Your Java Environment
import java.util.Properties;
public class JavaEnvironmentCheck {
public static void main(String[] args) {
Properties props = System.getProperties();
System.out.println("=== Java Environment ===");
System.out.printf("Java Version: %s%n", props.get("java.version"));
System.out.printf("JVM Vendor: %s%n", props.get("java.vendor"));
System.out.printf("JVM Name: %s%n", props.get("java.vm.name"));
System.out.printf("JVM Version: %s%n", props.get("java.vm.version"));
System.out.printf("JVM Info: %s%n", props.get("java.vm.info"));
System.out.printf("Runtime Name: %s%n", props.get("java.runtime.name"));
System.out.printf("Runtime Version: %s%n", props.get("java.runtime.version"));
System.out.printf("Java Home: %s%n", props.get("java.home"));
System.out.printf("Class Path: %s%n", props.get("java.class.path"));
// Show if running from JDK or JRE
String javaHome = props.get("java.home").toString();
boolean hasJavac = new java.io.File(javaHome + "/bin/javac").exists();
System.out.printf("Has Compiler (JDK): %b%n", hasJavac);
}
}
Output on a machine with only JRE installed:
=== Java Environment ===
Java Version: 17.0.9
JVM Vendor: Eclipse Adoptium
JVM Name: OpenJDK 64-Bit Server VM
JVM Version: 17.0.9+9
Runtime Name: OpenJDK Runtime Environment
Has Compiler (JDK): false ← This machine has JRE only!
Observability Checklist
- Metrics: JVM vendor, version, and bitness (32/64-bit)
- Logs: Java startup flags and environment variables on application boot
- Traces:
java -XshowSettings:all -versioncaptures all JVM settings - Alerts: Version mismatch between build-time JDK and runtime JRE
- Health Check:
java -versionandjavac -versionmust match for builds
Security and Compliance Notes
- Download JDK/JRE only from trusted sources — Oracle, Eclipse Temurin (Adoptium), Azul, or Amazon Corretto
- Java 8 update 201+ requires paid license for Oracle JDK — migrate to OpenJDK or Temurin to avoid costs
- JCE (Java Cryptography Extension) unlimited strength jurisdiction files may be required for AES-256 — install separately in some JDK versions
- Critical patches — Oracle releases quarterly patches; track via https://java.com/psupatch or adopt a JDK with automatic updates (Amazon Corretto, Azul Zulu)
Common Pitfalls and Anti-Patterns
| Pitfall | Why It Hurts | Fix |
|---|---|---|
| Installing JRE instead of JDK on dev machines | Cannot compile or run Maven/Gradle builds | Install JDK — it includes JRE |
| JAVA_HOME pointing to JRE instead of JDK | javac missing on command line | Verify $JAVA_HOME/bin/javac exists |
| Multiple JDK versions without cleanup | Builds use wrong compiler version | Use update-alternatives (Linux) or SDKMAN to manage |
| Assuming JVM vendor neutrality | HotSpot-specific flags fail on OpenJ9 | Stick to standard JVM flags |
| Downloading JDK from third-party sites | Bundled malware in installer | Use official java.com, adoptium.net, or aws.amazon.com/corretto |
Quick Recap Checklist
- JVM — The specification and implementation that executes bytecode; defines memory model, threading, and instruction set
- JRE — JVM + core class library (rt.jar); sufficient to run Java applications
- JDK — JRE + development tools (javac, jar, jdb, javadoc); needed to write and compile Java
- JDK includes JRE; JRE includes JVM — layered containment
- HotSpot, OpenJ9, GraalVM, and Azul Zing are different JVM implementations
- OpenJDK is the open-source reference implementation; Oracle JDK is a commercial derivative
- Development machines need JDK; production servers typically need only JRE (or minimal custom runtime)
- Verify
java -versionandjavac -versionmatch to avoid version mismatch errors - Use
jlinkto create minimal custom runtime images for containerized deployments
Interview Q&A
The JVM (Java Virtual Machine) is the runtime engine that executes bytecode — it interprets or JIT-compiles .class files to native machine instructions. The JRE (Java Runtime Environment) packages the JVM with the core class library (rt.jar) needed to run Java applications. The JDK (Java Development Kit) adds development tools like javac, javadoc, jdb, and jar on top of the JRE. If you are writing code, you need a JDK. If you are only running an app, the JRE suffices.
Yes — a JRE alone can run any compiled Java application (bytecode in .class or .jar files). The java launcher is part of the JRE. However, you cannot compile Java source code with only a JRE because javac is a JDK tool. A JDK includes the JRE, so with a JDK installed you can both compile and run Java programs.
HotSpot (Oracle, Eclipse Temurin) is the most widely used — it features tiered compilation with aggressive JIT optimizations and the widest range of GC algorithms (Serial, Parallel, G1, ZGC, Shenandoah). OpenJ9 (Eclipse) emphasizes fast startup and lower memory footprint, originally from IBM's J9 JVM. GraalVM (Oracle) adds ahead-of-time (AOT) compilation via native image, plus polyglot support for running JavaScript, Python, and Ruby alongside Java.
The JVM provides platform independence — the same .class bytecode runs on Windows, Linux, macOS, and embedded devices without recompilation. It also offers memory safety (no dangling pointers), security sandboxing for untrusted code, and dynamic class loading. The JVM's abstraction lets developers focus on portable code while JVM implementers optimize for each platform's hardware. JIT compilation at runtime can also apply CPU-specific optimizations that static compilation cannot anticipate.
Use the jlink tool to assemble a custom runtime image containing only the modules your application requires. First, determine your application's module dependencies with jdeps --print-deps myapp.jar. Then run: jlink --add-modules java.base,java.logging,java.sql --output myruntime. This produces a minimal image with only the specified modules, dramatically reducing size — useful for Docker containers where a full JDK/JRE would be wasteful.
Further Reading
- Oracle JDK Downloads — Official Oracle JDK binaries
- Eclipse Temurin (Adoptium) — Free OpenJDK binaries with LTS support
- SDKMAN! Documentation — Version manager for JDK, Gradle, Maven on Unix systems
- jlink Reference — Official docs for creating custom runtime images
- Amazon Corretto — Amazon’s no-cost, production-ready OpenJDK distribution
- Azul Zulu — Multi-platform OpenJDK with free LTS for embedded and cloud
The JDK ships as a superset of the JRE because developers also need to run applications during development — debugging, testing, and executing sample code are daily activities. Including the JRE means a single JDK installation handles both roles. You can think of it as: JDK = JRE + compilation tools. If you install only the JRE, the javac binary is simply not present in $JAVA_HOME/bin.
A JRE is a stripped-down runtime — it contains the JVM, core class library (rt.jar), and native libraries. A JDK adds the compiler (javac), debugger (jdb), profiler, documentation generator (javadoc), and build tools. In practice, JDK installations are 2–3x larger because they include all development binaries. For production servers running only compiled applications, the JRE (or a custom jlink image) is preferred to reduce footprint and attack surface.
JDK SE (Standard Edition) is for general-purpose desktop and server applications — it is what most developers install. JDK EE (Enterprise Edition) was deprecated in 2018; Jakarta EE is its successor for enterprise middleware and web services. JDK ME (Micro Edition) targets mobile devices, embedded systems, and IoT with a stripped-down class library and a smaller footprint VM. Most backend developers work exclusively with JDK SE, using Maven or Gradle for dependency management.
No — the Java compiler (javac) is a JDK tool, not a JRE tool. Attempting to run javac on a JRE-only machine produces bash: javac: command not found. The JRE only includes the java launcher and runtime library. For any compilation, debugging, or build activity, you need the JDK installed.
The JVM Specification defines the abstract runtime engine — bytecode instruction set, runtime memory model (heap, stack, PC registers, metaspace), thread synchronization primitives, and class file format. It does not mandate specific implementations of memory allocator, garbage collection algorithm, JIT compilation strategy, or thread scheduling. This is why HotSpot, OpenJ9, GraalVM, and Azul Zing all behave identically for the same bytecode but differ in performance characteristics, memory usage, and startup time.
OpenJDK is the open-source reference implementation of the Java SE specification, released under GPL v2 with the Classpath exception. Oracle JDK is Oracle's commercially supported distribution built on top of OpenJDK — it adds proprietary tools like Java Flight Recorder, Java Mission Control, and GraalVM Enterprise. For most use cases, OpenJDK (via Eclipse Temurin, Amazon Corretto, or Azul Zulu) is sufficient and free. Oracle JDK is chosen when commercial support and Oracle's additional tools are needed.
jlink assembles a custom Java runtime containing only the modules your application actually needs. You first use jdeps --print-deps myapp.jar to discover required modules, then run: jlink --add-modules java.base,java.logging,java.sql --output myruntime. The output is a minimal directory with a stripped-down JVM and only the JARs you depend on — often 30–60% smaller than a full JRE. This is ideal for containerized microservices where a full JDK/JRE would waste megabytes and expand the attack surface.
The javac -source and -target flags control which Java language version the compiler accepts and which bytecode version it emits. For example, -target 11 produces bytecode that any JVM 11+ can execute, regardless of whether the host JDK is 17. However, the runtime JVM must support features used — a Java 17 class file using string switches (introduced in JDK 14) will fail on a JDK 11 JVM. The bytecode version number (major.minor) is the real compatibility gate, not the -target flag alone.
GraalVM native image pre-compiles bytecode to machine code during the build phase via AOT (ahead-of-time) compilation, eliminating JVM startup and JIT warmup time. However, AOT compilation cannot apply runtime profiling data — it cannot do speculative inlining based on actual call frequencies or deoptimize and re-optimize based on runtime behavior. HotSpot's JIT compiler observes the running program and applies aggressive optimizations (inlining, scalar replacement, lock elision) that often outperform static AOT code for long-running, stable workloads.
JAVA_HOME is an environment variable that points to the JDK or JRE installation directory. Many build tools (Maven, Gradle, Ant), IDEs (IntelliJ, Eclipse), and scripts use $JAVA_HOME/bin/java or $JAVA_HOME/bin/javac to locate the Java runtime. If JAVA_HOME points to a JRE instead of a JDK, javac will be missing — you will see bash: javac: command not found when building. Tools that rely on the compiler will fail, even though java (runnable from the JRE) would still work.
The bytecode verifier is a critical security component that runs before any compiled code is executed. It checks that the .class file's instruction sequences are valid — ensuring no stack overflow, no invalid type casts, no accessing fields of objects that don't exist, and no violation of access modifiers. This prevents malicious .class files from crashing the JVM or bypassing the security sandbox. The verifier is why untrusted Java code cannot corrupt memory or escalate privileges — it is fundamentally impossible to compile a .class file that passes verification but contains unsafe operations.
Two approaches: (1) Check if javac exists in $JAVA_HOME/bin — if it does, it is a JDK. (2) From within the Java program itself, use new java.io.File(System.getProperty("java.home") + "/bin/javac").exists(). This is what the implementation snippet in this post demonstrates. Build servers and CI pipelines often use this check to fail-fast if a developer accidentally configured a JRE-only environment for a project that needs compilation.
Each LTS release removes deprecated APIs, adds new language features, and ships updated JVM implementations. JDK 8 introduced lambda expressions and the streams API. JDK 11 removed the Java EE module (JAX-WS, JAXB) and added HTTP Client (standardized in JDK 11). JDK 17 added sealed classes and pattern matching for switch. JDK 21 added virtual threads (project Loom) and record patterns. Newer LTS versions also include substantially improved GC algorithms (ZGC and Shenandoah became production-ready), better metaspace handling, and faster JIT compilers — upgrading the JVM version often yields free performance gains.
The classpath is a list of directories and JAR files that the JVM searches for class files at runtime. It is an unbounded, flat list — every JAR is examined in order until the class is found. The module path (Java 9+) enforces explicit module boundaries: each JAR must declare its module-info.java, and the module system validates reads and exports at compile and runtime. The module path prevents accidental cross-boundary dependencies and enables jlink to create minimal images. Legacy JARs without module-info still work on the classpath in "automatic module" mode, but lose the encapsulation benefits.
Yes — multiple JDK versions can be installed simultaneously in separate directories (e.g., /usr/lib/jvm/java-11 and /usr/lib/jvm/java-17). On Linux, update-alternatives --config java selects the system-wide default. On macOS, Oracle's JDK installer manages /Library/Java/JavaVirtualMachines/. SDKMAN! (sdk install java 17.0.9-temurin) and jenv are popular version managers that let you switch per-project via a .java-version file. Always ensure java -version and javac -version reference the same installation to avoid version mismatch errors.
Summary
JDK, JRE, and JVM are three distinct layers that serve different purposes — the JVM executes bytecode, the JRE provides the runtime library support, and the JDK equips you to build. Knowing which you need prevents wasted disk space on dev machines and production errors from version mismatches. With this layered model in mind, you can make informed choices about JVM implementations and deployment strategies.
Next: Now that you know the difference between JDK, JRE, and JVM, explore Java Primitive Types to understand how the JVM represents and manipulates fundamental data values.
Category
Related Posts
Abstract Classes in Java
Learn about partially implemented classes that define contracts for subclasses using abstract methods and concrete implementations.
Arithmetic Operators in Java
Master Java arithmetic operators: addition, subtraction, multiplication, division, and modulo with integer division gotchas and operator precedence explained.
Array Basics in Java
Learn Java array fundamentals: declaration, initialization, element access, and the length property explained simply.