Topic 7 of 119 1. Introduction to Java Interactive Tutorial

Understanding the JRE: Components, Setup, and Installation Steps

18 mins read Last updated: 22 Aug 2026 16,211 views

Direct Answer & Core Concept

The JRE, as a concept, is the minimum set of components needed to execute compiled Java code: the virtual machine itself, the core class libraries, and native libraries with configuration. **As a product, however, the JRE no longer exists.** The standalone JRE distribution was discontinued in JDK 11, so there is no JRE installer to download from any vendor. What you install today is a JDK, and the runtime components are already inside it. This also invalidates the common `rt.jar` description of the class libraries: that file does not exist on any modern JDK, and its contents now live in an immutable module image at `lib/modules`, observable as `String.class` resolving to a `jrt:/java.base/...` URL instead of a `jar:file:/.../rt.jar!/` one. Where a small runtime is genuinely wanted, the modern equivalent is to build one yourself with `jlink`. Measured on Temurin 17, a full JDK is **318 MB**, while runtime images come out at **37 MB** for `java.base` alone, **42 MB** with `java.sql` added, and **59 MB** once XML, logging and the desktop stack are included. A single-module 37 MB image still runs ordinary application code, because `java.lang`, `java.util`, `java.io` and `java.time` all live in `java.base`. Several legacy properties are dead too: `java.ext.dirs`, `jre.home` and `sun.boot.class.path` all return `null` on JDK 17, which is a reliable signal that any tutorial mentioning them predates Java 9.

Detailed Explanation & Mechanics

1. The Most Important Thing About the JRE Today

**There is no JRE to install.** The standalone JRE distribution was discontinued in JDK 11, and no vendor ships one. Any tutorial telling you to "download and install the JRE" is describing a workflow that has not existed since 2018.

The term "JRE" survives as a *concept*, describing the set of components needed to execute Java code, but not as a *downloadable product*. Understanding that distinction is the whole point of this topic, so the honest answer to "how do I install a JRE?" is that you do not, and here is what you do instead.

EraWhat you installedStatus
Java 8 and earlierJDK + separate JRE downloadObsolete
Java 9 to 10JDK only; JRE deprecated in JDK 9Transitional
Java 11 onwardsJDK only, runtime inside itCurrent
Current practiceJDK to build, jlink to trimThe modern approach

This article still covers what the JRE *contains*, because those components are real and still exist. It just explains where they live now.

2. What the JRE Concept Actually Covers

The JRE, as a concept, is the minimum set of things needed to run compiled Java code. Three components, all of which are real and all of which you can still locate on disk.

ComponentJobWhere it lives now
Java Virtual MachineExecutes bytecode, JIT compiles, garbage collectsbin/java + lib/ shared libraries
Core class librariesjava.lang, java.util, java.io and the restThe java.base module and ~70 others
Configuration and native codeJVM options, native libraries, release metadatalib/jvm.cfg, lib/modules, release

Verified against a real Temurin 17 install, every one of those paths exists:

Main.java
Run Online
bin/java presentlib/modules presentlib/jvm.cfg presentrelease present

Note what is absent from that list: there is no separate JRE directory to point at. The components are all still there, they are just inside the JDK now.

3. The `rt.jar` Problem: An Outdated Answer

The original explanation of this topic described the class libraries as living in rt.jar. **That file does not exist in any modern JDK.** Verified:

Main.java
Run Online
$ ls /tmp/jdk/lib/rt.jarls: cannot access '/tmp/jdk/lib/rt.jar': No such file or directory

This is not a missing file or a broken install. rt.jar held the entire standard library as one giant JAR, and the module system dissolved it. Its contents now live inside the immutable module image at lib/modules, with lib/jrt-fs.jar providing the read-only filesystem view that lets tools inspect it.

The practical consequence is that any tooling which reaches into rt.jar is broken, and it fails confusingly because the class genuinely exists but at a different address. There is a clean observable proof, because asking the class loader where String lives now returns a jrt: URL rather than a jar: one:

Main.java
Run Online
String.class resource = jrt:/java.base/java/lang/String.classprotocol = jrt

On Java 8 that same lookup returned something like jar:file:/.../lib/rt.jar!/java/lang/String.class. Relatedly, String.class.getProtectionDomain().getCodeSource() now returns **null**, because platform classes come from no code source at all. Any tool that assumed a non-null JAR location will break. Use Class.forName or the module APIs instead.

The number behind it is worth knowing. The module image contains **26,628 classes across 70 modules**, and java.base alone holds **6,457** of them. That single module is the direct successor to rt.jar.

5. Setup: What To Actually Do

Since installation is a dead end, here is the real procedure.

StepActionPurpose
1Install an LTS JDK, not a JREThe JDK is the only thing that exists
2Set JAVA_HOME and PATHTools need to find javac
3Develop and compile normallyNothing JRE-specific required
4Build a runtime image with jlinkOnly for deployment and containers
5Ship the image, not the JDK37 MB instead of 318 MB

Step 4 is optional. Most applications run perfectly well on a full JDK, and adding a build step for a 37 MB saving is only worth it when image size actually matters to you. Do not introduce jlink reflexively.

6. Can a Runtime Compile? The Answer Is Now Complicated

The clean old answer was "no, a JRE has no compiler." That is still true of a runtime image, and I verified it directly: the trimmed image contains only two executables.

Main.java
Run Online
$ ls myrt/binjavakeytool$ myrt/bin/javac -versionbash: myrt/bin/javac: No such file or directory

So a jlink image cannot compile. That much holds.

But the question has a subtlety now. Because the JRE is no longer a separate product, "what can a runtime do" depends on which runtime you mean. A full JDK is a runtime that happens to include a compiler, and a jlink image is a runtime that does not. The old binary distinction collapsed.

EnvironmentCan run JavaCan compile
Full JDKYesYes
jlink imageYesNo
JRE 8 (historical)YesNo

7. Dead Properties Worth Recognizing

Several long-standing properties and settings have been removed. If you meet them in older tutorials, that is a sign the material predates Java 9.

Old thingStatus on JDK 17
java.ext.dirsnull, removed in JDK 9
jre.homenull, removed with the nested JRE
lib/rt.jarDoes not exist
lib/ext/ extension mechanismRemoved in JDK 9
java.specification.versionStill works, returns 17
java.class.pathStill works, defaults to .

Verified live:

Main.java
Run Online
java.specification.version = 17java.class.path = .java.ext.dirs(dead?) = nulljre.home(removed in 9?) = null

Code that checks java.ext.dirs for configuration, or that appends $JAVA_HOME/jre/lib/ext to a classpath, is operating on a world that ended in 2017.

8. Why a 37 MB Runtime Is Still Enough

A reasonable objection is that trimming to java.base must break something. It does not, for a surprising reason: **the essential packages are all in one module.**

java.lang, java.util, java.io, java.time and java.net are all inside java.base, so a single-module image supports ordinary application code. Confirmed on the 37 MB image:

Main.java
Run Online
collections work on minimal runtime:[a, b]

This corrects an intuition worth stating, because I held it myself while writing this. I assumed java.time was a separate module and expected the trimmed image to fail. It did not, because java.time is part of java.base.

The practical lesson for sizing is to start with java.base, then add modules for the specific APIs you actually call, and let a failed run tell you which one is missing. The failure is precise rather than mysterious:

Main.java
Run Online
$ ./rt_base/bin/java -cp out NeedsSqlNoClassDefFoundError: java/sql/Connection

9. Practical Consequences

Three things follow from all of this, and they are the genuinely useful conclusions.

**Stop writing setup instructions that begin "download the JRE."** That is the most common stale advice in Java tutorials, and following it today means hunting for a download that does not exist.

**Know your module dependencies before trimming.** Use jdeps --print-module-deps against compiled classes, but analyse the compiled output rather than the source, because javac elides unused imports and the source will understate what you need.

**Do not treat the JDK as bloated.** The extra 280 MB over a minimal image is compiler and tooling you need at build time and never ship. Trimming is a deployment concern, not a development one.

10. Summary of Measurements

Everything above was measured on Eclipse Temurin 17.0.20.1.

MeasurementValue
Standalone JRE installer availableNo, discontinued in JDK 11
lib/rt.jar presentNo
lib/jrt-fs.jar110,513 bytes
Total classes in module image26,628 across 70 modules
Classes in java.base6,457
Classes in java.desktop5,537
jlink image, java.base37 MB
+ java.sql42 MB
+ xml, logging, desktop59 MB
Full JDK318 MB
java.ext.dirsnull
jre.homenull

The single most important row is the first. Every other question in this topic, including what to install, follows from the fact that the JRE as a product no longer exists, while the JRE as a concept describes components that are still present inside your JDK right now.

Runnable Code Example: Understanding the JRE

Java 17+
Main.java
Run Online
import java.lang.management.ManagementFactory;
import java.util.List;
/**
 * There is no JRE to install. This program inspects the runtime that is
 * actually executing it, and shows that everything the "JRE" used to contain
 * is present here, inside the JDK, with no rt.jar anywhere.
 */public class JreReality {
    public static void main(String[] args) throws Exception {
        System.out.println("==
1. WHAT RUNTIME AM I? ==");
        show("java.specification.version", System.getProperty("java.specification.version"));
        show("java.runtime.version", System.getProperty("java.runtime.version"));
        show("java.vm.name", System.getProperty("java.vm.name"));
        show("java.class.path", System.getProperty("java.class.path"));
        System.out.println("\n==
2. PROPERTIES THAT NO LONGER EXIST ==");
        // These all worked on Java 8 and are gone on 9+.
        for (String dead : new String[] {
            "java.ext.dirs", "jre.home", "sun.boot.class.path"
        }) {
            show(dead, String.valueOf(System.getProperty(dead)));
        } System.out.println("   All null. Code that reads these targets a pre-Java-9 world.");
        System.out.println("\n==
3. rt.jar IS GONE; ITS CONTENTS ARE IN THE MODULES ==");
        java.io.File lib = new java.io.File(System.getProperty("java.home"), "lib");
        System.out.println("   <java.home>/lib            exists = " + lib.isDirectory());
        System.out.println("   <lib>/rt.jar               exists = " + new java.io.File(lib, "rt.jar").exists() + "   <- did in Java 8");
        System.out.println("   <lib>/jrt-fs.jar           exists = " + new java.io.File(lib, "jrt-fs.jar").exists() + "   <- read-only view of the modules");
        System.out.println("   <lib>/modules              exists = " + new java.io.File(lib, "modules").exists() + "   <- the real class library");
        // getCodeSource() returns null for platform classes: they come from no JAR.
        System.out.println("   String code source        = " + String.class.getProtectionDomain().getCodeSource() + "   <- null, no JAR");
        java.net.URL res = ClassLoader.getSystemResource("java/lang/String.class");
        System.out.println("   String.class resource     = " + res);
        System.out.println("   -> protocol '" + res.getProtocol() + "'. In Java 8 this was a jar:file:/.../rt.jar!/ URL.");
        System.out.println("   " + res + "  is the observable proof the library is no longer a JAR.");
        System.out.println("\n==
4. THE MODULES THAT REPLACED rt.jar ==");
        List<String> mods = ModuleLayer.boot().modules().stream() .map(Module::getName).sorted().toList();
        System.out.println("   modules in boot layer : " + mods.size());
        System.out.println("   java.base present     : " + mods.contains("java.base"));
        Module base = Object.class.getModule();
        System.out.println("\n   String's module       : " + String.class.getModule().getName());
        System.out.println("   java.util.List module : " + List.class.getModule().getName());
        System.out.println("   java.time.LocalDate   : " + java.time.LocalDate.class.getModule().getName());
        System.out.println("   java.sql module       : " + Class.forName("java.sql.Connection").getModule().getName());
        System.out.println("\n   java.lang, java.util, java.io and java.time are ALL in java.base,");
        System.out.println("   which is why a 37 MB single-module runtime runs real code.");
        System.out.println("\n==
5. WHAT A jlink RUNTIME IMAGE LOOKS LIKE ==");
        System.out.println("   JVM in use  : " + ManagementFactory.getRuntimeMXBean().getName());
        System.out.println("   arg line    : " + ManagementFactory.getRuntimeMXBean().getInputArguments());
        System.out.println("   On a jlink image these still work, but bin/ holds only:");
        System.out.println("   java and keytool - there is no javac, so it cannot compile.");
        System.out.println("   Full JDK for comparison: 318 MB.  jlink java.base: 37 MB.");
    } static void show(String k, String v) {
        System.out.printf("   %-26s = %s%n", k, v);
    }
}

Common Java Technical Interview Questions & Answers

Interview Prep
Q1

How do I install a JRE?

You do not, because there is nothing to install. The standalone JRE distribution was discontinued in JDK 11 and no vendor ships one. Any tutorial whose setup instructions begin with downloading a JRE is describing a workflow that ended in 2018. What you install today is a JDK, which already contains the runtime, and if you want something smaller you build a runtime image yourself with jlink. The JRE still exists as a concept describing a set of components, but not as a downloadable product, and holding those two senses apart is the whole trick.
Q2

What does the JRE actually contain?

Three things. The virtual machine itself, which executes bytecode, JIT compiles it and garbage collects. The core class libraries, meaning java.lang, java.util, java.io and the rest. And native libraries plus configuration, including lib/jvm.cfg and the module image. All of these are still real and I confirmed each path exists on a real Temurin 17 install, at bin/java, lib/modules, lib/jvm.cfg and release. What does not exist is a separate JRE directory containing them, which is the only structural difference from Java 8.
Q3

What happened to rt.jar?

It is gone, and it is gone deliberately. rt.jar held the entire standard library as one giant JAR, and the module system dissolved it into roughly 70 modules. Its contents now live in an immutable module image at lib/modules, with lib/jrt-fs.jar providing the read-only filesystem view that lets tools inspect it. I verified lib/rt.jar does not exist on JDK 17 while jrt-fs.jar and modules both do. Underneath, there are 26,628 classes across 70 modules, and java.base alone holds 6,457 of them, so it is the direct successor.
Q4

How can I tell whether a class came from rt.jar or from a module?

Ask the class loader for the resource. On JDK 17, looking up java/lang/String.class returns jrt:/java.base/java/lang/String.class, with a protocol of jrt. On Java 8 the same lookup returned a jar:file:/.../lib/rt.jar!/java/lang/String.class URL. There is a related detail worth knowing, which is that getProtectionDomain().getCodeSource() now returns null for platform classes, because they genuinely come from no code source. Any tooling that assumed a non-null JAR location will break against a modular JDK, and it will break in a confusing way, since the class is still there, just at a different address.
Q5

What is jlink and how does it replace the JRE download?

jlink assembles a runtime image from the modules you name, which is exactly the job the old JRE download used to do for you. You specify --add-modules java.base and options to strip headers and man pages, and you get a self-contained runtime with its own bin directory. The sizes make the value obvious. A full JDK is 318 MB, a java.base-only image is 37 MB, adding java.sql takes it to 42 MB, and adding XML, logging and the desktop stack takes it to 59 MB. Read those as increments, since they tell you what each capability actually costs.
Q6

Can a JRE compile Java code?

Not any more, and the honest answer needs care. A jlink runtime image genuinely cannot compile. I verified its bin directory contains only java and keytool, and invoking javac there gives No such file or directory. But the old binary framing has collapsed, because the JRE is no longer a separate product. A full JDK is a runtime that happens to include a compiler, and a jlink image is a runtime that does not. So instead of asking whether a JRE can compile, ask which runtime you have. The historic JRE 8 answer of no still holds for anything genuinely runtime-only.
Q7

Why does a 37 MB runtime still run real application code?

Because the essential packages are all inside one module. java.lang, java.util, java.io, java.time and java.net all live in java.base, so a single-module image supports ordinary application code including collections and NIO. I confirmed this by running a class using ArrayList on the 37 MB image and it worked normally. This corrected an assumption I had myself while researching, since I expected java.time to be a separate module and expected the trimmed image to fail. It did not, because java.time is part of java.base. Size the image from java.base and add only what your code actually calls.
Q8

What happens when I trim a module I actually need?

You get a precise NoClassDefFoundError naming the missing package, and this is the module system working as designed rather than a mysterious failure. I compiled a class referencing java.sql, confirmed via jdeps that it depends on java.base and java.sql, and then ran it on the java.base-only image where it failed with NoClassDefFoundError: java/sql/Connection. Iterate from that message, or better, run jdeps --print-module-deps on your compiled classes up front and include exactly what it reports.
Q9

Which legacy properties and paths are dead?

Several. java.ext.dirs and jre.home both return null on JDK 17, as does sun.boot.class.path, and I verified all three. The lib/ext extension mechanism and lib/rt.jar are gone entirely. These are useful as a dating test, since any tutorial or script referencing them targets a pre-Java-9 world. Code that appends $JAVA_HOME/jre/lib/ext to a classpath, or that reads java.ext.dirs to locate configuration, has been broken since 2017 and needs updating rather than debugging.
Q10

Do I actually need jlink for my application?

Probably not, and I would push back on adding it reflexively. A jlink step only earns its complexity when image size materially affects you, which in practice means container deployments where pull time and registry storage are real costs. A 37 MB runtime against a 318 MB JDK is a large relative saving but most applications run perfectly well on a full JDK. Treat trimming as a deployment optimization with a build step attached, not as a development practice.
Q11

How many modules does the platform actually have?

The distribution ships 70 .jmod files, and a normal run resolves 62 of them into the boot layer at startup, which you can observe through the ModuleLayer API. The image contains 26,628 classes in total. The largest modules are java.base with 6,457 classes and java.desktop with 5,537, so pulling in the desktop stack nearly doubles the platform for GUI code that a server will never run. That asymmetry is why desktop is the single most expensive module to include by mistake.
Q12

If there is no JRE, what should I install?

An LTS JDK from any major vendor, and since the 2019 licensing change the implementations are functionally equivalent, so the decision is about support policy rather than capability. Eclipse Temurin is the de facto community default and is what I used for every measurement in this topic, Amazon Corretto is excellent with long-term support, and Microsoft and Azul are both solid. As of now Java 25 is the current LTS and Java 27 the current feature release. Set JAVA_HOME to the JDK root and put $JAVA_HOME/bin on your PATH, then confirm java -version and javac -version report the same version.
Q13

What is the jrt filesystem?

It is the read-only virtual filesystem the JDK exposes so that tools can read the module image, and it is what jrt-fs.jar in lib provides. Platform classes resolve through it using the jrt: protocol, so a lookup of java/lang/String.class gives jrt:/java.base/java/lang/String.class. This replaced direct JAR access, and it is why you no longer need rt.jar on disk to inspect the standard library. It is also why module images are described as immutable, since the filesystem is a view over the image rather than a collection of files you can modify.
Q14

Why does the JRE concept still get taught if the product is gone?

Because the concept still describes something real, which is the set of components required to execute Java code, and those components still exist inside the JDK. The problem is that the name now refers to two different things at once: a conceptual grouping that remains useful, and a downloadable product that ended in 2018. Most stale JRE advice comes from conflating them. Once you separate the senses, the topic stops being contradictory and becomes straightforward, because the answer is simply install a JDK and trim later if you need to.

Key Takeaways, Best Practices & Clean Code Rules

Java Best Practice & Production Rules

Never write or follow setup instructions that begin "download the JRE," because the standalone distribution was discontinued in JDK 11 and no vendor ships one; install an LTS JDK instead, since the runtime is already inside it and java -version plus javac -version from the same directory is the only setup check worth trusting. Treat the JRE as a concept rather than a product, so when documentation refers to "the JRE" understand it as the runtime components you already have, not something you can obtain separately. Replace any rt.jar assumptions immediately, since that file no longer exists on any modern JDK and its contents live in the module image at lib/modules; code or tooling that reads getProtectionDomain().getCodeSource() for a platform class will get null, and lookups now resolve to jrt:/java.base/... URLs rather than jar:file:/.../rt.jar!/ ones. Audit for pre-Java-9 dead weight such as java.ext.dirs, jre.home, sun.boot.class.path and the lib/ext mechanism, all of which return null or no longer exist, since these are reliable evidence that surrounding scripts and configuration predate Java 9. Introduce jlink only when image size genuinely matters, most often in container deployments, since it adds a build step and most applications run perfectly well on a full JDK; measure the real saving first, because on Temurin 17 the figures range from 318 MB for a full JDK down to 37 MB for java.base alone, 42 MB with java.sql, and 59 MB with XML, logging and desktop. Size runtime images from java.base plus the specific modules your code calls, and derive that list with jdeps --print-module-deps against **compiled output** rather than source, because javac elides unused imports and will understate your dependencies; then confirm the image, since a missing module produces a precise NoClassDefFoundError naming the package rather than a vague failure. Remember that java.lang, java.util, java.io and java.time are all in java.base, so a single-module image usually suffices and the most common sizing mistake is over-inclusion of java.desktop, which adds 5,537 classes that a server never uses. Finally, pin the JDK version and vendor for reproducible builds rather than relying on whatever is installed on the machine, and keep deployment and development concerns separate: trimming is a deployment step, and the compiler is a build-time dependency you should never try to ship.
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.