Project Valhalla is OpenJDK's proposal to rethink how the JVM represents objects. Twelve years after its inception, its central component —JEP 401, "Value Classes and Objects" —is already circulating as preview functionality in early-access builds, ahead of its planned arrival in JDK 28 in March 2027. The aim was to go beyond theory and test it practically, with an early-access build of JDK 27 (27-jep401ea3), to see how realistic the much-touted performance promises really are.
What is Project Valhalla and why does it matter?
Project Valhalla began in 2014 as a research project within OpenJDK, with an ambitious goal: to eliminate one of the oldest structural costs of the JVM—the fact that every object, no matter how small, carries an identity and lives behind a pointer. JEP 401 is the proposal that finally brings this idea to fruition, and it has already been confirmed for integration into the main branch of OpenJDK. For teams running backend services on the JVM, especially those with a high density of small, short-lived objects, this represents one of the most significant performance optimizations in a long time.
The journey to get here has been long, and it's not over yet:
- 2014 — Project Valhalla begins as a research project.
- 2022 — JEP 401 (Value Classes and Objects) is published.
- 2025-2026 — the first early-access builds with JEP 401 support for public testing arrive; the one used in this demo is one of them.
- JDK 27 (September 2026) — is released without Valhalla; it remains available only in early-access builds like the one used in this test.
- JDK 28 (March 2027) — JEP 401 arrives as a preview feature in an official version.
- JDK 29 (likely LTS, September 2027) — Java architect Brian Goetz has already warned that expecting JEP 401 to come out of preview for that version would be "optimistic".
That last warning is important for any team evaluating this: even after becoming an official preview, it's reasonable to plan for a prolonged evaluation phase — on the order of 12 to 18 months — before thinking about bringing value classes into critical production.
JEP 401 is just the first step. Two components depend on it: specialized generics, which would allow representing elements like List
What are value classes?
A value class is a type whose instances lack object identity: two instances with the same data are indistinguishable from each other, something that can be verified in code with the new `Objects.hasIdentity(Object)` method. This gives the JVM the freedom to represent them much more compactly than a traditional object. Specifically, the model enables three optimizations:
- Smaller memory footprint: by not needing identity, the JVM can do without the object header and store values more densely.
- Heap flattening: elements are stored contiguously in memory, like an array of structs, instead of an array of pointers to objects scattered around the heap.
- Scalarization: When a value class does not "escape" a method, the JIT can avoid reserving it on the heap altogether and keep it in registers.
In preview mode, the JDK itself migrates some commonly used classes—Integer, LocalDate, among others—to behave as value classes. Developers can also declare their own using the `value class` keyword , intended for domain types that represent raw data: coordinates, monetary amounts, timestamps, composite IDs. However, this improvement isn't automatic across all code. According to the project documentation, the benefit is particularly concentrated in:
Dense data structures and large collections.
- Arrays traversed sequentially .
- Intensive computation on pure data types (coordinates, dates, amounts).
- Services with high volume of small, short-lived instances: payment processing, event pipelines, trading systems, real-time analytics.
For traditional business logic, with few instances and without that high-density pattern, the impact
expected is marginal.
The demo: ValhallaBenchmark, a testbed for the JDK's value classes
To make the comparison fair, two classes with traditional identity were handwritten that have exactly the same fields as their JDK counterparts, to isolate the effect of identity and nothing else:
- BoxedInt, equivalent to Integer: a single int field.
- Ymd, equivalent to LocalDate: three int fields (year, month, day).
The demo, ValhallaBenchmark.java, does three things for each pair of classes:
- Print Objects.hasIdentity(...) on one instance of each type, to put it in black on white
who has an identity and who does not. - Measure memory and time when building a large array (5 million elements for Integer, 2.5
millions for LocalDate, which weighs more). - Measures the throughput of traversing the array and summing its values , with a warm-up.
to allow the JIT to optimize before timing.
How to run it
The early-access build used (27-jep401ea3+1-1) comes from the official Project Valhalla team distribution, published at jdk.java.net/valhalla. This is the same channel where Oracle periodically releases new test builds of JEP 401 based on an incomplete version of the next JDK release—in this case, JDK 27. Simply download the package corresponding to your operating system, extract it to any folder, and point the script's JDK variable to that location: it requires no installation and does not replace the production JDK.
The two demo files (ValhallaBenchmark.java and run_benchmark.bat) are located in the same folder. Before running:
- Check that the JDK variable within run_benchmark.bat points to the installation of the earlyaccess build (default C:\jdk-27).
- Run .\run_benchmark.bat from PowerShell or cmd in that folder.
The script compiles and runs with the necessary preview flags:
The parameters --n, --warmup and --iters are adjustable at the beginning of the .bat file itself if you want to repeat the test with other sizes.
The script also logs garbage collector activity in out\gc-benchmark.log, thanks to the flag Xlog:gc*:file=...:time,uptime,level,tags included in run_benchmark.bat. Each line records the event's time (time and uptime), the level of detail (level), and the garbage collector's category (tags, such as [gc] or [gc,heap]). It's primarily useful for externally verifying the reliability of the demo's memory measurements.
Results
With this change of focus, the numbers do show the expected profits from Valhalla, from
clear and reproducible form between executions:
One detail worth noting: constructing the arrays was slower for value classes than for identity classes (Integer: 48 ms vs. BoxedInt: 24 ms; LocalDate: 51 ms vs. Ymd: 44 ms). This makes sense—construction was measured only once, without warm-up, while summation was measured after 10 warm-up iterations, giving the JIT time to take advantage of flattening. The pattern is consistent: value classes cost slightly more to construct the first time, but once in the array, they occupy less memory and are traversed significantly faster.
Conclusions
- Valhalla's gains are real, but not automatic: they depend on the specific class already having mature flattening support in the build being used. With Integer and LocalDate—the value classes that Oracle migrated and optimized— up to 2.5x less memory and 2.4x more throughput were observed per iteration.
- Practical recommendation: Don't assume gains simply by declaring a value class. It's advisable to benchmark against your own workload, in your own build, before planning any migration—just as is recommended for any other low-level optimization.
- It remains a preview feature. It will be released in JDK 28 and, according to Java architect Brian Goetz, will likely continue in preview even in JDK 29 (LTS). It's not yet time to deploy it to critical production; rather, it's a time for experimentation and familiarization.
Patricio Flores
Software Technician
ALTIA