Core Java

Project Valhalla in Practice: Writing Your First Value Class

JEP 401 is real, it’s in an early-access build today, and you can run it right now. It just isn’t landing where most people think it is.

A decade of conference talks, name changes, and rewritten prototypes has led to this: JEP 401, Value Classes and Objects, has real syntax, a real JVM implementation, and a real early-access build you can download today. Before writing a single line, it’s worth getting one detail straight, because it’s been misreported enough that it’s worth correcting directly.

Correcting the record: JEP 401 is not targeted at JDK 26. As of this month, it has been integrated into the OpenJDK mainline targeting JDK 28, due in March 2027, as a preview feature. Early-access builds tied to it have existed for a while, some built on JDK 26 and JDK 27 codebases along the way, but the destination release has now been locked in as JDK 28. The early-access build available right now lives at jdk.java.net/28, and that’s what this walkthrough uses.

Getting the Early-Access Build Running

Download the JDK 28 early-access build, unzip it, and point your terminal at its bin directory. Every command from here on needs the preview flag, since value classes are a preview feature, disabled by default even in this build.

export JAVA_HOME="$PWD/jdk-28/Contents/Home"
export PATH="$JAVA_HOME/bin:$PATH"

java --version
# openjdk 28-ea ...

jshell --enable-preview

Everything that follows, both the javac compile step and the java run step, needs --enable-preview on the command line. Forget it on either side and the JVM refuses to run the class file.

Declaring Your First Value Class

The simplest way in is a value record, since records already give you final fields and a generated constructor for free. Add the value modifier and the class loses its identity:

jshell> value record Point(int x, int y) {}
created record Point

jshell> Point p = new Point(17, 3)
p ==> Point[x=17, y=3]

jshell> Objects.hasIdentity(p)
$1 ==> false

jshell> new Point(17, 3) == p
$2 ==> true

That last line is the one worth sitting with. For an ordinary class, == compares references, two separately constructed objects are never ==, no matter what fields they hold. For a value object, == has nothing to compare but state, so two Point instances built from the same coordinates are simply equal. Objects.hasIdentity(), added alongside this JEP, is the straightforward way to check which kind of object you’re holding, and it turns out a handful of existing JDK classes, including Integer and LocalDate, already flip over to value classes the moment preview mode is on.

Ordinary classes can declare themselves as value classes too, not just records, as long as every field is final:

value class Money {
    private final long cents;
    private final String currency;

    Money(long cents, String currency) {
        this.cents = cents;
        this.currency = currency;
    }

    long cents() { return cents; }
    String currency() { return currency; }
}

What You Lose: Identity and Synchronization

Giving up identity is not a cosmetic change, it removes real capabilities the object used to have, and the JVM enforces the loss rather than just discouraging it.

CapabilityIdentity classValue class
Meaningful reference equality (==)Yes, compares memory identityNo, compares field state instead
synchronized blocks or methodsAllowedDisallowed; the JVM rejects locking on a value object
System.identityHashCode()Meaningful, stable per objectNot meaningful, there’s no identity to hash
WeakReference / SoftReferenceSupportedThrows an exception; there’s no single canonical instance to hold weakly
Mutable fieldsAllowedDisallowed; every field must be final

The synchronization loss deserves a second look, since it’s the one most likely to surprise a team migrating an existing class. If any code path anywhere locks on an instance of a type you’re converting to a value class, that code stops compiling, or stops working, the moment the conversion lands. It’s a genuinely useful early-warning signal, in most cases a class holding pure immutable data was never a sensible thing to synchronize on anyway, but it means the migration is not always as simple as adding one keyword.

What You Gain: Flat Memory Layout and JIT Inlining

The payoff for giving up identity is that the JVM no longer has to guarantee a value object lives at one canonical heap address. Without that guarantee, the runtime is free to embed the object’s fields directly into whatever’s holding it, a containing object’s fields, or consecutive slots in an array, instead of storing a pointer to a separately allocated block of memory. This is the mechanism the Valhalla team calls heap flattening, and it’s the reason value classes exist at all, not the syntax, the flattening.

Illustrative memory cost per element for an array of 1,000,000 two-int Point objects, based on documented JVM object layout rules (8-byte compact header, 8-byte array reference, 8 bytes of int fields). Actual savings depend on JIT decisions and array size.

An array of a million ordinary Point objects stores a million 8-byte references, each pointing to a separately allocated object carrying its own header plus its two int fields. An array of a million flattened value Points can, when the JIT chooses to flatten it, store the sixteen bytes of actual field data back-to-back with nothing else, no header per element, no pointer indirection, no separate allocation to chase during a cache miss. That’s the concrete shape of the win Valhalla has been chasing since 2014: primitive-like storage density for something that still looks and behaves like a class.

Benchmarking the Difference

Oracle’s own walkthrough for this early-access build includes a small, repeatable demo worth reproducing rather than taking on faith. It runs a date-heavy computation across several iterations, once using ordinary identity objects and once using value objects for the same data, and reports the timing for each pass. In that walkthrough’s own results, later iterations using value objects settled into roughly the mid-20-millisecond range, down from around 40 milliseconds on the first pass, as the JIT warmed up and began applying flattening-related optimizations that simply aren’t available to identity objects.

How to run your own version of this benchmark

Treat any single machine’s numbers as directional. Oracle’s own walkthrough is explicit that results vary by hardware and array size, this is preview software, and JIT heuristics around flattening are still being tuned.

Write the same computation twice, once against a plain class, once against its value class equivalent, changing nothing but that one modifier.

Run each version for enough iterations to let the JIT warm up, a handful of passes at minimum, and discard the first few results before measuring.

Prefer a proper microbenchmarking harness like JMH over hand-rolled System.nanoTime() loops if you’re publishing the numbers anywhere.

From Project Valhalla’s start to a preview build you can download today.

What We Learned

JEP 401 gives Java a real, runnable way to declare a class that trades away object identity for a flatter, cheaper memory representation, and after a decade of design churn, that trade is finally something you can compile and run against an early-access build rather than read about in a talk. Declaring one takes a single value modifier on a class or record with only final fields, but the cost is concrete, not cosmetic: no synchronizing on the instance, no meaningful reference equality, no weak references, enforced by the JVM rather than left to convention.

The reward is equally concrete, once the JVM can stop guaranteeing a canonical address for an object, it can flatten that object’s fields directly into arrays and containing objects, cutting out header and pointer overhead entirely for data-heavy workloads. The one thing worth being precise about, given how much of the coverage has gotten it wrong, is where this actually lands: JEP 401 is a preview feature targeting JDK 28 in March 2027, not JDK 26, even though the early-access build to try it on is sitting there right now.

Eleftheria Drosopoulou

Eleftheria is an Experienced Business Analyst with a robust background in the computer software industry. Proficient in Computer Software Training, Digital Marketing, HTML Scripting, and Microsoft Office, they bring a wealth of technical skills to the table. Additionally, she has a love for writing articles on various tech subjects, showcasing a talent for translating complex concepts into accessible content.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button