The JVM Weekly explainer frames JDK 28 as the payoff for twelve years of work, citing OpenJDK benchmarks showing 3-5x speedups on tight numerical loops and ~2x on hash-map-heavy workloads once keys migrate to value classes. The argument is that flattening instances into fields, arrays, and stack frames eliminates pointer-chasing overhead that has long made Java's object model slower than C# structs for data-heavy workloads.
The article emphasizes that value classes have no header word, no synchronization monitor, and no `==` reference semantics — meaning code that synchronizes on instances or relies on identity equality will behave differently or fail to compile when types migrate. This is presented as the necessary trade-off for JIT flattening, but also as the migration hazard that has kept the project in preview for years.
The piece argues that value classes alone aren't enough — without `!` and `?` markers giving the VM static nullability information, the JIT still has to reserve sentinel slots and emit null checks. JDK 26's preview of null-restricted types is framed as the missing piece that lets `Point[]` truly become an `int[]`-shaped layout in memory, which is why both JEPs are landing together in JDK 28.
JVM Weekly published a long explainer on Project Valhalla, the OpenJDK effort to add value types to Java, framing JDK 28 (targeted March 2027) as the release where the bulk of the user-visible work finally lands. The piece walks the twelve-year arc from the 2014 `LW1` prototype through the current preview state of JEP 401 (Value Classes and Objects) and JEP 402 (Null-Restricted and Nullable Value Class Types), both of which have been iterating in `--enable-preview` form across the last several JDK releases.
The short version of what Valhalla actually ships: a new kind of class declared with the `value` modifier that has no object identity. No identity means no header word, no synchronization monitor, no `` reference semantics, and — critically — the JIT is free to flatten instances directly into fields, arrays, and stack frames.== A `value record Point(int x, int y) {}` stored in a `Point[]` becomes a true `int[]`-shaped layout in memory, not an array of pointers to heap-allocated boxes. This is the thing C# devs got in 2002 and Java devs have been waiting for since Brian Goetz's first "Codes like a class, works like an int" talk.
The companion piece — null-restricted types — adds `!` and `?` markers to value class usages (`Point! origin`, `Point? maybe`), giving the VM enough static information to elide null checks and pack arrays without sentinel slots. JDK 26 (March 2026) shipped the preview that most teams are testing against today; JDK 28 is when the feature is expected to drop the preview flag.
The headline pitch is performance, and the numbers are real. Early Valhalla benchmarks from the OpenJDK team show 3-5x speedups on tight numerical loops over `Complex[]` or `Point[]`, and roughly 2x on hash-map-heavy workloads once the key type migrates to a value class. That gap exists because today's `HashMap
But the part the marketing decks bury is the migration story for existing types. The plan — and this is the part worth slowing down for — is to retroactively convert `java.lang.Integer`, `Long`, `Double`, `Optional`, `LocalDate`, and a long list of "obviously valuelike" JDK classes into value classes. This is what the Valhalla team calls B2/B3 migration, and it is where the legacy semantics start fighting back.
Value classes don't have identity. That means three things that compile fine today become either runtime errors or undefined behavior:
- `synchronized(someInteger)` — the lock target has no monitor. Current preview builds throw `IdentityException` at runtime; JDK 28 is expected to keep that behavior and add a `javac` warning.
- `System.identityHashCode(boxedLong)` — defined to return the identity hash, which doesn't exist. Returns the value hash instead, breaking any code that relied on identity-distinct hashing across equal values.
- `WeakHashMap
`synchronized` on a boxed primitive has been a code smell since Java 5; Valhalla is about to escalate it from "FindBugs warning" to "production exception." The same logic applies to any library code that does `synchronized(Optional.empty())` as a cheap monitor, or that uses interned `Long` values as lock tokens — patterns that genuinely exist in old Hadoop, Cassandra, and Akka code paths.
Community reaction on the JEP mailing lists has been mostly relieved-it's-finally-happening, with one persistent thread of concern: library authors who don't migrate become a tax on everyone downstream. If your Jackson, Guava, or Spring dependency keeps `Integer` boxing in its hot paths because it never gets recompiled against value-aware bytecode, your application doesn't get the flattening even if your own code is clean. Valhalla's value is multiplicative across the dependency graph or it isn't there at all.
If you ship a Java service that you expect to still be running in 2027, three concrete actions are worth doing now, while JDK 26 preview gives you a free test environment:
Grep for `synchronized(` on anything that could be a boxed primitive. Most won't be — `synchronized(this)` and `synchronized(SOME_FINAL_OBJECT)` are fine. The dangerous patterns are `synchronized(map.get(key))`, `synchronized(someLongValue)`, and `synchronized(Optional.of(x))`. Treat each hit as a future incident.
Audit any `IdentityHashMap` or `WeakHashMap` where the key type is in the migration list. The fix is usually to switch to a regular `HashMap` with explicit lifecycle management, or to wrap the key in a holder class that retains identity. This is the single highest-leverage cleanup most Java codebases can do before JDK 28, because it's a quiet correctness bug that becomes a loud one.
Stop relying on `Integer` interning for equality shortcuts. The `-128` to `127` `Integer` cache has been a footgun for two decades; with value classes, `Integer.valueOf(200) == Integer.valueOf(200)` becomes implementation-defined rather than guaranteed-false. Code that depended on either answer needs `.equals()`.
For library maintainers, the calculus is different: the question is whether to opt into `value class` declarations for your own types in 2026 (preview, locks you to JDK 26+) or wait for JDK 28 LTS-adjacent stability. The Valhalla team's guidance is to ship value classes behind a major version bump, since the binary compatibility story for downstream consumers depends on their JDK too.
JDK 28 is the target, not a guarantee — Valhalla has slipped from every previously announced release since JDK 11, and the team has earned the right to slip again. But the preview surface area in JDK 26 is large enough that the semantic decisions are effectively frozen; what's left is performance tuning, edge-case JIT work, and the politically delicate question of which legacy JDK classes migrate in 28 versus 29. The interesting consequence isn't that Java gets faster — it does, modestly — but that twelve years of "will this ever ship" finally collapses into a concrete migration deadline. Audit your `synchronized` blocks now while it's free.
Top 10 dev stories every morning at 8am UTC. AI-curated. Retro terminal HTML email.