JRE curation: what's in the Eliya JRE and why
The Eliya JRE downloads as a 49 MB tarball and extracts to 74 MB. It includes the standard java.se module set plus nine additional jdk.* modules (Flight Recorder, JMX, jcmd attach, PKCS#11, DNS SPI, and a few others). This page explains how the compact size is achieved and why we chose this module set.
JDK vs JRE: the structural picture
The JDK
The Java Development Kit is the full distribution: HotSpot (the JVM, in libjvm.so), the standard Java class library (java.* modules), the JDK tooling modules (jdk.*: Flight Recorder, JMX/RMI agent, jcmd attach, PKCS#11, the javac compiler, javadoc, jlink itself, etc.), and the CLI tools in bin/ (java, javac, jcmd, jlink, etc.). A typical JDK is ~300 MB.
What "Java SE" means as a module
Java SE is the standardised Java surface: the API portfolio in the Java Language Specification plus the Java Platform Specification. As a module, Java SE is named java.se in OpenJDK: an aggregator that requires every other java.* module (java.base, java.compiler, java.datatransfer, java.desktop, java.logging, java.management, java.net.http, java.rmi, java.sql, java.xml, and the rest of the SE module set). That is what --add-modules java.se to jlink gives you, and it is the API portfolio every "Java SE 25 compliant runtime" must expose.
The gap: jdk.* modules are NOT in Java SE
The JDK also ships modules that are outside the Java SE specification: JDK tooling modules with the jdk.* prefix:
| Module | What it provides |
|---|---|
jdk.unsupported | sun.misc.Unsafe, sun.misc.Signal: "load-bearing" for the JVM ecosystem (Netty, Caffeine, Hazelcast, Lucene rely on it) |
jdk.jfr | Flight Recorder: continuous low-overhead profiling, the standard JVM observability primitive |
jdk.management.agent | JMX/RMI agent: for Prometheus exporters and monitoring tools |
jdk.attach | The Attach API for jcmd, jconsole, or any tool to attach to a running JVM |
jdk.crypto.cryptoki | SunPKCS11: HSM / hardware-token crypto access |
jdk.naming.dns | DNS SPI for JNDI |
jdk.localedata | Non-en_US locale resources (i18n) |
jdk.zipfs | The jar: NIO Path provider |
A Java SE-compliant JRE (i.e. jlink --add-modules java.se) does not include any of these. They are JDK tooling modules outside the Java SE specification.
This is the source of the apparent paradox the next section resolves: vendors that ship a "JRE" usually ship java.se only. Their JRE lacks Flight Recorder, JMX, and jcmd attach. The Eliya JRE adds them by default.
What jlink does
jlink is the OpenJDK tool that assembles a custom JRE-like runtime image from a chosen set of modules. Inputs:
- A module path (where to find module JARs / JMODs)
- A list of root modules (
--add-modules X,Y,Z) - Options like
--strip-debug,--no-man-pages,--no-header-files,--compress
jlink traces the transitive requires of the root modules, copies the resulting module closure plus the JVM runtime image, and produces a directory tree that looks like a JRE (bin/java, lib/, conf/). The resulting tree runs independently; it doesn't need a JDK on the host.
The --strip-debug flag
The --strip-debug option strips debug attributes from the class files inside the runtime image (line-number and local-variable tables); it does not strip native symbols from the shared libraries. The class code still works; you just lose class-file debug metadata. The size win comes from module curation plus class-file stripping, not from shrinking libjvm.so, which ships with its native symbols intact.
The --no-man-pages and --no-header-files flags
These drop man/man1/*.1 (the man-page snippets that are mostly redundant with --help) and include/*.h (the JNI header files for native code authors). Production servers don't read man pages from the runtime image; they get them from the OS man database.
Eliya's curation: the 10 modules
The Eliya JRE includes:
| Module | Why included |
|---|---|
java.se | The Java SE aggregator: pulls in every standard Java SE module |
java.net.http | HTTP/2 client (explicit; also pulled via java.se, listed for visibility) |
jdk.unsupported | Effectively required by the JVM ecosystem (Netty, Caffeine, Hazelcast, …) |
jdk.crypto.cryptoki | Forward-looking: HSM-backed crypto needed for Phase 4 hardware-token work |
jdk.jfr | Flight Recorder: the OpenJDK community has standardised on JFR for production profiling. Operators expect jcmd <pid> JFR.start to work without switching to the JDK. |
jdk.management.agent | JMX/RMI agent: Prometheus exporters, monitoring agents, and operational tools attach via JMX |
jdk.attach | The Attach API: local jcmd works without it; remote / external tool attach requires it |
jdk.naming.dns | DNS SPI for JNDI |
jdk.localedata | Non-en_US locales: i18n surface for production servers handling international data |
jdk.zipfs | The jar: NIO Path provider: tools that walk into ZIP/JAR archives via NIO |
Combined with --strip-debug, --no-man-pages, and --no-header-files, the resulting image is ~48 MB compressed (~74 MB extracted).
How the size stays compact
The Eliya JRE adds 9 extra modules on top of the standard java.se set and extracts to 74 MB. Two jlink mechanisms account for most of the size win:
- Module curation. The full upstream OpenJDK 25 module set is around 80 modules. Eliya's JRE includes only the modules production servers use (the
java.seaggregator plus the ninejdk.*modules listed below). Modules not included are not present in the assembled runtime image at all. - JIMAGE compression. jlink's
--compressoption compresses the class files insidelib/modulesat build time. The compression cost is paid once at build; runtime decompression happens on demand and is negligible.
The 9 extra jdk.* modules add a few megabytes collectively. Net: a 49 MB compressed tarball that extracts to 74 MB with a more complete production module set than a java.se-only baseline.
Note: jlink's --strip-debug flag strips class-file debug attributes inside the modules, not native symbols. The native shared libraries (libjvm.so, etc.) retain their symbols in this build.
Trade-offs
Every choice has costs. Eliya's JRE choices and their trade-offs:
| Choice | Cost |
|---|---|
--strip-debug | Can't gdb into HotSpot from a stripped JRE. Mitigation: install the Eliya JDK alongside if you need to debug, or use the JDK download. |
Include jdk.jfr, jdk.management.agent, jdk.attach | ~5-7 MB heavier than a Java-SE-only JRE; some consumers don't need diagnostics. Mitigation: future Eliya variants (Docker -slim, -jre-distroless) can omit these. |
Include jdk.localedata | ~10 MB i18n resources for non-en_US locales. Many server deployments only need en_US. Mitigation: the future -slim variant drops this. |
Include jdk.crypto.cryptoki | ~1 MB PKCS#11 provider. Not all deployments use HSMs. Mitigation: low marginal cost; left in. |
--no-man-pages, --no-header-files | JNI code authors who compile against the runtime image need separate headers. Mitigation: use the Eliya JDK for native dev. |
The non-Eliya alternative: jlink your own
The architectural beauty of jlink is that any user can produce their own custom runtime image from any vendor's JDK. Customers don't need the Eliya JRE; they could run:
…using the Eliya JDK, the Temurin JDK, the Corretto JDK, or any other vendor's JDK. The result is a runtime they own. Eliya's JRE saves customers that step and provides a curated, signed, reproducible default. The argument for Eliya's JRE is convenience + the reproducibility/signing/audit-trail story attached to it, not "you can't do this yourself".
Forward-looking
Eliya plans four container variants, each with its own module curation:
| Variant | Module set | Target use |
|---|---|---|
| Full JDK (shipping in 25.0.3) | Everything in the JDK | Developer machines, CI builders |
-slim (Phase 2) | Eliya JRE minus jdk.localedata and other non-server modules | Container deployments, en_US production |
-jre (Phase 2) | The Eliya JRE as described on this page | Container-native applications |
-jre-distroless (Phase 2) | The Eliya JRE on gcr.io/distroless/base for minimal attack surface | Security-conscious containers |
When Eliya transitions to JDK 27 (post-Phase 1), the module set will be re-validated against JDK 27's module changes. JDK 27 may add modules (e.g. structured concurrency improvements) we want to include; we may also drop modules whose use case has receded.