Java JRE
The JRE (Java Runtime Environment) is the part of Java needed to run a compiled Java program, as opposed to writing and compiling one. It consists of the Java Virtual Machine plus the standard class library and supporting files. Understanding the JRE explains what a program actually depends on when it runs, and why modern Java deployment no longer relies on a separately installed JRE.
What the JRE Contains
JRE = JVM + standard class library + runtime support files
| Component | Role |
|---|---|
| JVM | Loads, verifies, and executes bytecode; manages memory and threads |
| Class library | The ready-made classes programs call: java.lang, java.util, java.io, java.net, java.time, and the rest |
| Configuration | Security settings, trusted certificate authorities, time-zone data, character encodings |
The java launcher | The command that starts a JVM with a program |
What it does not contain: javac, jar, javadoc, jshell, and the other development tools. Those are only in the JDK.
┌─────────────────────────────── JDK ───────────────────────────────┐
│ javac jar javadoc jshell jlink jcmd ... │
│ ┌──────────────────────────── JRE ────────────────────────────┐ │
│ │ class library (java.base, java.sql, java.net.http, ...) │ │
│ │ ┌───────────────────────── JVM ─────────────────────────┐ │ │
│ │ │ class loader · verifier · interpreter · JIT · GC │ │ │
│ │ └───────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────┘
Why the Class Library Matters at Runtime
A compiled .class file does not contain the code of the library classes it uses. It contains only references to them by name. Consider:
import java.time.LocalDate;
public class Today {
public static void main(String[] args) {
System.out.println(LocalDate.now());
}
}
After compiling, Today.class is tiny. It records that it calls java.time.LocalDate.now() and java.io.PrintStream.println(Object), but the code for those methods lives in the runtime's class library. When the program runs, the JVM loads LocalDate and PrintStream from the runtime installed on that machine.
Two consequences:
- The runtime version must support every API the program uses. A program calling a method added in Java 21 fails on a Java 17 runtime with
NoSuchMethodError, even if it was compiled successfully on a Java 21 JDK. - Library bug fixes and security patches arrive without recompiling. Updating the runtime from 25.0.1 to 25.0.2 changes the library code every program uses, with no change to the programs themselves.
The Version Check at Startup
Every .class file records the version of the class-file format it uses. A runtime refuses to load classes newer than itself:
# compiled with JDK 25, run on a Java 17 runtime
java Today
Error: LinkageError occurred while loading main class Today
java.lang.UnsupportedClassVersionError: Today has been compiled by a more recent
version of the Java Runtime (class file version 69.0), this version of the
Java Runtime only recognizes class file versions up to 61.0
The class-file version is the Java version plus 44: Java 17 is 61, Java 21 is 65, Java 25 is 69. The fix is to run on a newer runtime, or compile for the older one:
javac --release 17 Today.java
History: From Separate JRE to Bundled Runtime
Until Java 8, there were two downloads:
- The JRE for end users, who installed it once system-wide so that any Java application or browser applet could run.
- The JDK for developers.
This model had problems. Applications depended on whatever JRE version happened to be installed. Updating the shared JRE could break one application while fixing another. Browser applets and Java Web Start, the main reasons for a system-wide JRE, were deprecated and then removed.
Since Java 11, Oracle and most vendors no longer publish a standalone JRE. The modern practice is that each application ships with its own runtime:
| Approach | How |
|---|---|
| Custom runtime image | jlink builds a runtime containing only the modules the app needs |
| Native installer | jpackage bundles the app and a jlink runtime into .msi, .pkg, .deb, or .rpm |
| Container image | A Docker image based on a JDK or JRE-style base image (for example, the eclipse-temurin:<version>-jre images) |
| Server with a JDK | The operations team installs a specific JDK version on the server, managed like any other dependency |
Some vendors (Eclipse Temurin, Amazon Corretto, Azul Zulu) still publish JRE-only packages or container images for convenience, which are smaller because they omit the development tools. Functionally they are what this lesson calls the JRE.
Building a Runtime with jlink
jlink produces a runtime image that is effectively a custom JRE:
# 1. Find out which modules the app uses
jdeps --print-module-deps --ignore-missing-deps app.jar
# java.base,java.sql
# 2. Build a runtime with only those modules
jlink --add-modules java.base,java.sql \
--output app-runtime \
--strip-debug --no-man-pages --no-header-files --compress=zip-6
# 3. Run the app with it
app-runtime/bin/java -jar app.jar
app-runtime contains a bin/java launcher, the JVM, and only the listed modules. It has no javac. It can be copied to a machine with no Java installed and run there (on the same operating system and CPU architecture it was built for).
The --compress option names vary between JDK versions (older versions used numeric levels such as --compress=2); check jlink --help for your JDK.
Runtime Configuration and System Properties
The runtime exposes information about itself through system properties:
public class RuntimeInfo {
public static void main(String[] args) {
String[] keys = {
"java.version", "java.vendor", "java.home",
"os.name", "os.arch", "file.encoding", "user.timezone"
};
for (String k : keys) {
System.out.printf("%-14s %s%n", k, System.getProperty(k));
}
Runtime rt = Runtime.getRuntime();
System.out.println("CPUs: " + rt.availableProcessors());
System.out.println("Max heap (MB): " + rt.maxMemory() / (1024 * 1024));
}
}
Example output:
java.version 25.0.1
java.vendor Eclipse Adoptium
java.home /usr/lib/jvm/temurin-25-jdk-amd64
os.name Linux
os.arch amd64
file.encoding UTF-8
user.timezone Asia/Kolkata
CPUs: 8
Max heap (MB): 3980
java.home is the root of the runtime running this program. Runtime.getRuntime() returns an object describing the running JVM; maxMemory() reports the largest heap the JVM will attempt to use, which by default depends on the machine's physical memory. Values like user.timezone and file.encoding affect program behavior and can differ between a developer's laptop and a production server, which is a common source of "works on my machine" problems.
Properties can be set when launching:
java -Duser.timezone=UTC -Dapp.env=production -jar app.jar
A program reads custom properties with System.getProperty("app.env"). This is one way to pass configuration into an application without changing its code.
JRE in Practice
- On a developer machine: install a JDK. It includes everything the JRE has.
- On a server running one application: install a specific JDK or JRE package version, or ship a
jlinkruntime with the app. - In containers: use a JRE-style base image for smaller images, or a JDK image if you need diagnostic tools like
jcmdinside the container. - For desktop applications: use
jpackageto bundle a runtime so users do not need to install Java at all.
The guiding principle is that an application's runtime version should be chosen deliberately and tested, not left to whatever happens to be installed on the machine.