Valhalla, Now!

Developer Advocate

Java Team at Oracle

Reset

You’ve probably heard a lot about Project Valhalla:

  • indirection and flattening

  • object headers and density

  • identity and custom primitives

  • universal and specialized generics

  • performance benefits

Forget all that for now!

We will start where Java stands today.

Value Classes

Then we discuss:

  • JDK value classes

  • custom value classes

  • restrictions and behavior

  • optimization opportunities

Value Classes in JDK 28

All that is available in JDK 28:

But only with --enable-preview.

(Otherwise, even JDK value classes are regular classes.)

Nota Bene

All quotes come from JEP 401: Value Objects (Preview).

A must-read before you start using value classes!

Outlook

At the end, we can discuss
upcoming features from Project Valhalla.

Valhalla, Now!

Object Identity
Value Types
Declaration
Behavior
Recommendations
Optimization

Distinguishing Variables

Which of these variable pairs can be distinguished?

int a = 20260819;
int b = 20260819;

var x = LocalDate.of(2026, 8, 19);
var y = LocalDate.of(2026, 8, 19);
  • a and b can’t

  • x and y can

⇝ How?

Identity

LocalDate (and all other reference types) have identity,
a way to distinguish otherwise identical objects.

var x = LocalDate.of(2026, 8, 19);
var y = LocalDate.of(2026, 8, 19);

var identical = x == y; // false

synchronized (x) {
	// x is "locked", y isn't
}

Identity of Mutable Objects

For mutable objects, identity is important: It lets us distinguish two objects that have the same state now but may have different states in the future.

var list1 = new ArrayList<String>();
var list2 = new ArrayList<String>();

var equal = list1.equals(list2); // true
var identical = list1 == list2;  // false

⇝ Mutable objects can be equal but never interchangeable.

Identity of Immutable Objects

But equal immutable objects are usually interchangeable.

var x = LocalDate.of(2026, 8, 19);
var y = LocalDate.of(2026, 8, 19);

Does it make sense to distinguish interchangeable objects?

Confusing Identity: Integer

Integer x1 = new Integer(0);
Integer y1 = new Integer(0);
var identical1 = x1 == y1; // ?
Integer x2 = 0;
Integer y2 = 0;
var identical2 = x2 == y2; // ?
Integer x3 = 2026;
Integer y3 = 2026;
var identical3 = x3 == y3; // ?

Confusing Identity: String

var x1 = new String("0");
var y1 = new String("0");
var identical1 = x1 == y1; // ?
var x2 = "0";
var y2 = "0";
var identical2 = x2 == y2; // ?
var x3 = "0";
var y3 = "" + RandomGenerator.getDefault().nextInt(1);
var identical3 = x3 == y3; // ?

Performance Cost

Tracking identity is expensive.

Escape analysis exists to avoid it.

Summary

Current state:

  • all reference types have identity

  • identity distinguishes objects

  • many equal immutable objects are interchangeable

  • for many immutable classes:

    • identity is meaningless

    • identity can cause confusion

    • identity comes with a run-time cost

Wouldn’t it be nice to be able to opt out of identity?

Valhalla, Now!

Object Identity
Value Types
Declaration
Behavior
Recommendations
Optimization

Value Classes

Enter JEP 401 in JDK 28 with --enable-preview:

We introduce value objects to model simple immutable data. A value object is an instance of a value class, declared with the value modifier. Classes without the value modifier are identity classes, and their instances are identity objects.

Platform Value Classes

In package java.lang :

Integer, Long, Float, Double, Byte, Short, Character, Boolean, Number, Record

In package java.util :

Optional, OptionalInt, OptionalLong, OptionalDouble

In package java.time :

LocalDate, LocalTime, LocalDateTime, ZonedDateTime, OffsetTime, OffsetDateTime, Duration, Instant, Period, Year, YearMonth, MonthDay

In package java.time.chrono :

MinguoDate, HijrahDate, JapaneseDate, ThaiBuddhistDate

First Experiments

var x = LocalDate.of(2026, 8, 19);
var y = LocalDate.of(2026, 8, 19);

var identical = x == y; // true

// synchronized (x) {
//     ⇝ compile error:
//       required: a type with identity
// }

First Experiments

Integer x1 = new Integer(0);
Integer y1 = new Integer(0);
var identical1 = x1 == y1; // true

Integer x2 = 0;
Integer y2 = 0;
var identical2 = x2 == y2; // true

Integer x3 = 2026;
Integer y3 = 2026;
var identical3 = x3 == y3; // true

Bad News

The String class, due to some dependencies on object identity in its API and implementation, is not a value class, so instances of String are always identity objects.

More Platform Value Classes

Project Valhalla is exploring the option
to migrate more JDK classes to value classes:

It’s worth asking which other JDK API classes might want to become value classes someday. I explored the question in conjunction with the CSR (JDK-8339199), and want to memorialize the experience here, for future reference.

Custom Value Classes

You can declare your own value classes by applying the value modifier to any class whose instances should be immutable and interchangeable:

Immutable — All instance fields of the class are final, and the value represented by an instance will not change over time; and

Interchangeable — It is not necessary to distinguish between two separately-created instances that represent the same value.

Custom Value Classes

value class ComplexNumber {
	// ...
}

value record Point(int x, int y) {

}

(Fun fact: The only new syntax is value.)

Business as Usual

In most respects, value objects work the way that objects have always worked in the language: They have fields and methods, they are handled by reference, and their references can be null.

Summary

Value classes are conceptually simple:

  • value classes:

    • are reference types

    • opt out of identity

    • are immutable and interchangeable (!)

  • 30 JDK value classes

  • custom value classes with value

But there are a few implications.

Valhalla, Now!

Object Identity
Value Types
Declaration
Behavior
Recommendations
Optimization

Restrictions

Value classes are immutable and interchangeable,
which restricts some class-building capabilities.

Immutability

Value classes are immutable,
so all fields are implicitly final.

(Truly final - even to reflection.)

Inheritance

Limited superclasses:

A value class can extend either java.lang.Object or an abstract value class, but not an identity class.

No subclasses:

The class itself is also implicitly final, so it cannot be extended.

Side Track

Java 25 introduced the early construction phase:

class ComplexNumber extends Number {

	ComplexNumber(double r, double i) {
		// early construction
		this.r = r;
		this.i = i;
		super();
		// late construction
		LOGGER.log("Created: " + this);
	}

}

By default, code runs during late construction.

Side Track

During early construction:

  • can write and read fields

  • no other access to this
    (e.g. no instance method calls)

⇝ Object can be initialized before other code "sees" it.

Value Class Construction

For value classes:

  • by default, code runs during early construction

  • call to super(…​) or this(…​):

    • requires that all fields are set

    • is needed to enter late construction

⇝ Initial mutation of final fields can never be observed.

Business as Usual

Except for the aforementioned restrictions,
inheritance works as for identity classes:

  • have Object at their inheritance hierarchy root

  • can implement interfaces

  • can be abstract
    (interpret as "abstract class is value-ready")

Business as Usual

Everything else works as for identity classes, too:

  • inherit equals, hashCode, toString from Object

  • no restriction on the types of fields
    (particularly both value abd identity classes work)

Summary

Declaration site restrictions:

  • fields and class are final

  • can’t extends identity classes
    (except java.lang.Object)

  • by default, construction code runs
    during early construction

Everything else works as usual for declaration.

But there are behavioral differences.

Valhalla, Now!

Object Identity
Value Types
Declaration
Behavior
Recommendations
Optimization

Restrictions

Value classes lack identity,
which changes/prevents identity-based operations.

New Objects

For identity classes, new guarantees an instance
that’s not == to any other.

For a value class, invoking the new operator might not allocate a fresh object.

⇝ Conceptually, every constructor could act
like a caching factory method.

Deep Reflection

Value class fields are truly immutable.

Using deep reflection, as embodied in the setAccessible and set methods of the java.lang.reflect.Field API, to mutate the fields of a value object is not supported. […​] Libraries must initialize instances of a value class using the class’s constructors.

Indistinguishability

Historically, == compares identity.

After the introduction of value objects, the purpose of the == operator is to test whether two referenced objects are indistinguishable.

For identity objects:

  • indistinguishable and identical are the same

  • the evolving definition has the same semantic

Indistinguishability

The == operator compares value objects by comparing the objects' field values, so references to two objects are == if the objects have identical field values.

That means:

  • for primitive fields: bitwise equality

  • for reference type fields: indistinguishability

⇝ For value class fields, == is recursive.

Object Monitors

Operations like wait, notify, and synchronize
interact with an object’s monitor.

Object monitors are tied to an object’s identity.

⇝ These operations do not work on value objects.

(Compile error if possible, otherwise run-time exception.)

Finalization

The finalize method of a value object is never invoked by the garbage collector. javac issues identity warnings for value classes that override finalize.

(Finalization is deprecated for removal - JEP 421 ⑱)

Serialization of Value Records

Serialization of record instances
works through constructor invocation.

Serialization of value records works automatically

Serialization of Non-Records

Otherwise, serialization creates instances with all-null fields
and assigns them later, but value objects prohibit this.

Serialization of non-record value classes requires manual intervention. Value classes that implement Serializable must implement the writeReplace and readResolve methods so that a replacement object is serialized and deserialized in place of the value object itself.

⇝ Otherwise, InvalidClassException.

GC Interaction

The garbage collection APIs in the java.lang.ref package and the java.util.WeakHashMap class cannot be used with value objects. Attempting to create Reference objects for value objects will cause an IdentityException to be thrown.

Your Operations

If your operations depend on an object’s identity, you can:

  • check with Objects::hasIdentity

  • verify with Objects::requireIdentity

Business as Usual

Everything else works as for identity classes, too, e.g.:

Methods that operate on Object or Object[] parameters accept value objects. Almost anywhere you need to provide an implementation of an interface, the implementation may be a value class. Generic types such as List<T> and Comparable<T> can be instantiated with value classes as the type arguments.

Summary

Behavioral differences vs identity classes:

  • new may not allocate a fresh object

  • == is field-wise comparison

Behavioral restrictions:

  • no deep reflection

  • no object monitor interaction

  • no finalization

  • limited serialization (records or proxies)

  • no GC interaction

What other benefits can we get?

Valhalla, Now!

Object Identity
Value Types
Declaration
Behavior
Recommendations
Optimization

Getting Started

As a general rule, if a class with immutable state does not require identity then it is probably appropriate to adopt the value modifier. This includes abstract classes.

Value Class Design

Keep value class APIs small:

enough logic to construct, validate, and transform instances

Equality vs ==

To test whether two value objects represent the same value, use the equals method. When declaring a class, define equals in a way that always returns true for interchangeable instances.

Values != Records

Values aren’t records:

  • values give up identity for
    safety and performance

  • records give up encapsulation for
    succinctness and usability

But many classes need neither
identity nor encapsulation.

Values ♥ Records

Considerable overlap between values and records:

  • both immutable

  • both (often) represent simple data

  • both (often) have simple APIs

⇝ Value records will be very common
(probably the norm).

Valhalla, Now!

Object Identity
Value Types
Declaration
Behavior
Recommendations
Optimization

✋ Stop Right There!

Value classes have performance implications,
but value should not be used as a "speed-up keyword".

  • JVM applies optimizations

  • these depend on many factors

  • they will evolve over time

Optimizing value objects will become part
of regular JDK performance work.

References

At run time, a JVM can optimize value objects by encoding references to them in more compact forms than references to identity objects. Instead of allocating space in the heap for a value object, a JVM can flatten and scalarize the reference to the object.

Reference Flattening

When a field of one object, or an element of an array, stores a reference to another object, a JVM can encode the other object’s field values directly into the reference. When this is done, the reference is not a pointer to the other object in memory. The reference is said to be flattened.

This usually requires an extra bit to encode nullity.

Reference Flattening

An array of LocalDate references can be flattened by prepending a null flag to the year-month-day triple of a LocalDate object (an int and two byte-s).

+--------------+
| LocalDate[5] |
+--------------+
| 1|2026|08|16 |
| 1|2026|08|17 |
| 1|2026|08|18 |
| 0|0000|00|00 |
| 1|2026|08|20 |
+--------------+

Reference Flattening

class Event {
	private LocalDate day;
}
# POINTER REFERENCE   ║ # FLATTENED REFERENCE
┌                     ║ ┌
| EVENT  ┌            ║ | EVENT
| day -> | LOCALDATE  ║ | day[1|2026|08|19]
└        | 2026|08|19 ║ └
         └            ║

Reference Flattening

Values still need to be written atomically.

This is generally limited to 64 bits.

Due to the nullity bit, this limits reference flattening
to values classes with total fields <64 bits. 😕

Future Improvements

Flattening would be more applicable with:

  • 128-bit atomic writes

  • language-level atomicity control

  • language-level nullity information

  • generic specialization (JEP 218)

Reference Scalarization

When passing objects to or from a method,
the runtime generally references heap objects.

For identity objects, escape analysis can prove
that the identity doesn’t need to be tracked.

⇝ The runtime can load fields once
and use the stack to pass them.

Reference Scalarization

Value objects lack identity,
so this can always be done.

Unlike for flattening,
no strict boundary exists.

At the JVM’s Discretion

Reference flattening and scalarization are optimizations, not language features. You cannot directly control them. Like all optimizations, they are done at the discretion of the JVM. There are, however, things you can do to make it more likely that a JVM can apply these optimizations.

Variable Declaration

Only references that are declared as value classes can be optimizied, e.g.:

// flattenable
Integer[] ints = { 1996, 2006, 1996, null, null };
// not flattenable
Object[] objs = { 1996, 2006, 1996, null, null };

// field is not flattenable
record Box<T>(T field) { }
// field stores a heap pointer
var b = new Box<LocalDate>(i);

Summary

Optimization of value objects:

  • JVM can flatten and scalarize some references

  • flattening is limited to small values

  • scalarization is more universally applicable

Advice

As (almost) always in Java:

  • write maintainable code

  • use constructs semantically

  • rely on JVM for optimization

So long…​

37% off with
code fccparlog

bit.ly/the-jms

More

Slides at slides.nipafx.dev
⇜ Get my book!

Follow Nicolai

nipafx.dev
🦋 🐘 /nipafx

Follow Java

inside.java // dev.java
/java    //    /openjdk

Image Credits