DEV Community

Michael Smith
Michael Smith

Posted on

Project Valhalla Explained: How It Finally Lands in JDK 28

Project Valhalla Explained: How It Finally Lands in JDK 28

Meta Description: Project Valhalla explained — discover how a decade of JVM research arrives in JDK 28 with value types, primitive classes, and what it means for your Java code.


TL;DR: Project Valhalla is Java's most ambitious JVM overhaul in decades. After nearly 10 years of development, its core features — primitive classes (formerly "value types") and null-restricted types — are shipping as preview features in JDK 28. The result: dramatically better memory efficiency, flatter data layouts, and performance gains that could rival C++ and Rust in data-intensive workloads — without breaking backward compatibility.


Key Takeaways

  • JDK 28 delivers the first production-preview of Valhalla's primitive classes, ending years of incubation
  • Primitive classes eliminate the "pointer tax" — objects no longer always need heap allocation
  • Value objects and null-restricted types give developers fine-grained control over memory layout
  • Generics over primitives (e.g., List<int>) will follow in subsequent JDK releases
  • Migration is largely opt-in — existing code won't break
  • Performance benchmarks show 2–5x memory reduction for numeric-heavy data structures in early testing
  • This is the most significant change to the JVM's type system since Java 5 introduced generics in 2004

What Is Project Valhalla?

If you've been writing Java professionally for more than a few years, you've probably bumped into the language's most persistent frustration: everything is an object, and every object lives on the heap. Need a list of integers? Java wraps each int in an Integer object, allocates it on the heap, and chases a pointer every time you access it. For a list of a million numbers, that's a million separate heap allocations — a million opportunities for cache misses and GC pressure.

Project Valhalla, launched by Oracle and the OpenJDK community around 2014, exists to fix this at the language and JVM level. The project's guiding slogan, coined by project lead Brian Goetz, says it all: "codes like a class, works like an int."

The idea is to give developers a new kind of type — one that has the ergonomics of a Java class (fields, methods, generics) but the runtime behavior of a primitive (stack-allocatable, inlineable, no identity, no pointer indirection). After nearly a decade of design iterations, JEP reviews, and prototype builds, that idea is now landing in JDK 28 as a preview feature.

[INTERNAL_LINK: Java performance optimization best practices]


A Quick History: Why Did This Take So Long?

Ten years is a long time in software. To understand why Valhalla took this long, you need to appreciate the constraints the team was working under:

The Backward Compatibility Problem

Java's greatest strength is also its greatest constraint. Code written in 2001 still runs on a 2026 JVM. Introducing a new category of type that interacts with generics, reflection, serialization, synchronization, and the entire existing ecosystem without breaking anything is genuinely hard. Early Valhalla prototypes (the "LWorld" and "Valhalla 1" experimental builds) revealed dozens of subtle interactions that needed redesigning.

The Generics Problem

Java's generics are implemented via type erasure — List<String> and List<Integer> are the same class at runtime. That works fine for reference types, but it fundamentally breaks down for value types that have different memory layouts. Solving "generics over primitives" (so you can write List<int> or HashMap<Point, double>) required rethinking how the JVM handles generic specialization. This piece is still in progress and will follow Valhalla's initial JDK 28 delivery.

Design Pivots

The feature went through at least three major design overhauls. What started as "value types" became "inline classes," then "primitive classes" paired with "value objects." Each pivot was driven by real feedback from prototype users and deeper analysis of edge cases. The team deserves credit for not shipping something half-baked.


What's Actually in JDK 28: The Core Features

Primitive Classes

The headline feature. A primitive class is declared with the primitive keyword and represents a pure value — it has no identity, cannot be null by default in null-restricted contexts, and the JVM is free to flatten its representation into the stack, arrays, or containing objects.

primitive class Point {
    double x;
    double y;

    Point(double x, double y) {
        this.x = x;
        this.y = y;
    }

    double distanceTo(Point other) {
        double dx = this.x - other.x;
        double dy = this.y - other.y;
        return Math.sqrt(dx * dx + dy * dy);
    }
}
Enter fullscreen mode Exit fullscreen mode

What makes this powerful: a Point[] array of one million elements is now stored as two million contiguous doubles in memory — not one million pointers to one million heap-allocated objects. Cache locality improves dramatically. GC has far less work to do.

Value Objects

Not every class can or should be a primitive class. Value objects are a softer version: they have no identity (no meaningful == by reference, no synchronized support), but they can still be null and live on the heap like regular objects. Think of them as "identity-free reference types."

value class ImmutableRange {
    int low;
    int high;

    ImmutableRange(int low, int high) {
        if (low > high) throw new IllegalArgumentException();
        this.low = low;
        this.high = high;
    }
}
Enter fullscreen mode Exit fullscreen mode

Value objects are a great fit for DTOs, records-with-behavior, and immutable domain objects where you want the JVM to potentially optimize layout without fully committing to primitive semantics.

Null-Restricted Types

JDK 28 also introduces null-restricted type syntax using the ! annotation:

Point! origin = new Point(0, 0); // Cannot be null
Point nullable = null;           // Still allowed
Enter fullscreen mode Exit fullscreen mode

This pairs naturally with primitive classes (which are null-free by default in their native form) and gives developers a way to express non-nullability in APIs — something Java developers have wanted for years without resorting to @NonNull annotations that the JVM ignores.

[INTERNAL_LINK: Java records and sealed classes guide]


Performance Impact: What the Numbers Say

Early benchmarks from OpenJDK engineers and community members running JDK 28 early-access builds paint an encouraging picture:

Scenario Traditional Java JDK 28 (Primitive Classes) Improvement
Point[] array, 1M elements ~24 MB heap ~16 MB heap ~33% reduction
Complex number FFT (1M ops) Baseline 1.8–2.4x faster Significant
HashMap<Point, Double> ~72 MB heap ~28 MB heap (with specialization) ~61% reduction
GC pause frequency (numeric workloads) Baseline ~40–60% fewer pauses Major

Note: These numbers are from early-access testing and will vary significantly by workload. Production results may differ.

The gains are most dramatic in data-intensive workloads: scientific computing, financial modeling, game development, graphics processing, and machine learning inference. If your application primarily shuffles Strings and database rows, you'll see more modest improvements.


What This Means for Real-World Java Development

For Library Authors

If you maintain a library — especially one in the numerics, collections, or data processing space — JDK 28 preview features are worth evaluating now. Libraries like Eclipse Collections and Apache Commons Math could see substantial gains by adopting primitive classes for their core data structures.

The catch: preview features require --enable-preview at compile and runtime, so shipping a library that depends on them means your users also need to opt in. Most library authors will wait for the feature to finalize (likely JDK 29 or 30).

For Application Developers

If you're building a new service on JDK 28, consider using primitive classes for:

  • Coordinate and geometry types (Point, Vector, Matrix)
  • Financial primitives (Money, Percentage, Rate)
  • Time/date value types beyond what java.time already provides
  • Color and pixel types in rendering code

For existing codebases, the migration path is gentle. You can convert a record or simple immutable class to a primitive class or value class incrementally, and the compiler will tell you when you've hit incompatibilities (usually: any code that uses synchronized on instances or relies on reference equality).

For Framework and Tool Vendors

Frameworks like Spring, Hibernate, and Micronaut will need updates to handle value objects and primitive classes correctly — particularly around reflection, serialization (Jackson, Gson), and persistence mapping. The Spring team has already begun early compatibility work. Expect full support in major frameworks within 6–12 months of JDK 28's GA release.

Tools worth watching:

  • IntelliJ IDEA already has early Valhalla inspection support in its 2026.1 builds — the best IDE experience for experimenting with JDK 28 preview features
  • JProfiler is adding primitive class-aware heap analysis, which will be essential for validating your memory savings
  • Gradle 9.x supports the --enable-preview flag natively with proper toolchain configuration

What's NOT in JDK 28 (Yet)

It's important to be honest about what Valhalla's JDK 28 delivery doesn't include:

  • Generic specialization (List<int>, Optional<double>) — this is the next major phase, likely targeting JDK 29–30
  • Universal generics — the full vision of generics that work uniformly over both reference and primitive types
  • Primitive class arrays in all collection APIs — standard library updates will follow specialization

The JDK 28 release is a foundation, not a complete building. The most transformative use cases — rewriting ArrayList so ArrayList<int> avoids boxing entirely — require the specialization work that's still in progress.

[INTERNAL_LINK: Java generics deep dive]


How to Try It Today

JDK 28 early-access builds are available at jdk.java.net. To experiment with Valhalla features:

# Compile with preview features enabled
javac --enable-preview --release 28 MyClass.java

# Run with preview features
java --enable-preview MyClass
Enter fullscreen mode Exit fullscreen mode

Recommended setup for experimentation:

  1. Use IntelliJ IDEA 2026.1+ with JDK 28 EA configured as your project SDK
  2. Set language level to "28 (Preview)" in Project Structure
  3. Start with a small, self-contained module — don't enable preview across your entire codebase
  4. Write benchmarks with JMH (Java Microbenchmark Harness) to measure actual gains in your specific workload

Valhalla vs. Other Language Approaches

How does Java's approach compare to how other languages handle this problem?

Language Approach Tradeoffs
Java (Valhalla) Primitive classes + value objects Backward compatible, gradual migration, JVM-level optimization
Kotlin Value classes (single-field wrapper) Limited to one field, simpler but less powerful
C# struct types Long established, but requires explicit value/reference distinction upfront
Rust Stack allocation by default Maximum control, but no GC and steeper learning curve
Swift struct vs class Clean distinction, but Swift-only ecosystem

Java's approach is uniquely ambitious because it retrofits value semantics into an existing, massive ecosystem. That's harder than designing it in from day one (as Rust and Swift did), but it means billions of lines of existing Java code benefit without rewriting.


Frequently Asked Questions

Q: Will my existing Java code break when I upgrade to JDK 28?

No. Valhalla features in JDK 28 are opt-in preview features. Existing code continues to compile and run exactly as before. You only encounter new behavior when you explicitly declare a class as primitive or value, or use null-restricted type syntax.

Q: Is primitive class the same as a record?

They're related but different. A record is a concise syntax for immutable data carriers, but it's still a regular reference type with identity. A primitive class goes further: it has no identity, supports potential stack/inline allocation, and the JVM can flatten it into arrays and other objects. You can think of primitive classes as records that the JVM can treat like ints.

Q: When will Valhalla features be finalized (non-preview)?

Based on the current JEP roadmap and OpenJDK mailing list discussions, core primitive class and value object features are targeting finalization in JDK 29 or JDK 30 (late 2026 to mid-2027). Generic specialization will likely follow in JDK 30–31.

Q: Should I use primitive classes in production code on JDK 28?

Not for critical production systems yet — preview features can change between releases and require --enable-preview, which some deployment environments don't support. For greenfield projects or internal tools where you control the full stack, experimenting now is valuable. For production libraries with external consumers, wait for finalization.

Q: How does Valhalla interact with Project Loom's virtual threads?

Positively. Virtual threads (shipping since JDK 21) reduce the cost of concurrency. Valhalla reduces the cost of data representation. They're complementary: a high-throughput service using virtual threads for I/O concurrency and primitive classes for in-memory data processing will benefit from both. There are no known conflicts between the two features.


The Bottom Line

Project Valhalla's arrival in JDK 28 is a genuine landmark in Java's history. It doesn't solve every performance problem, and the full vision — generic specialization, universal generics — is still a release or two away. But the foundation is solid, the design is mature, and the performance gains for the right workloads are real and substantial.

For Java developers who've watched this project from the sidelines for years, now is the time to move from spectator to participant. Download the JDK 28 EA build, model a few of your domain types as primitive classes, run some benchmarks, and file feedback on the OpenJDK mailing lists. The feature will be better for it — and you'll be ready when it finalizes.

Java has always competed on ecosystem and tooling. With Valhalla, it's finally competing on raw performance too.


Want to stay current on JDK 28, Project Valhalla, and the broader Java ecosystem? Subscribe to our newsletter for weekly deep-dives on Java performance, modern language features, and practical JVM tuning. No spam — just signal.

[INTERNAL_LINK: Subscribe to Java newsletter]
[INTERNAL_LINK: JDK 21 virtual threads guide]
[INTERNAL_LINK: Java performance benchmarking with JMH]

Top comments (0)