Daily-It

개발, AI, 인프라, 자동화와 일상 IT 제품 후기를 직접 써보며 정리하는 기술 블로그입니다.

Java value class vs record: how Project Valhalla changes the object model

Summary

If you are comparing Java value class vs record, the important point is not only syntax. Java records already make DTOs and read-only data carriers much shorter, but Project Valhalla’s value class aims at a different layer: the JVM object model.

The key difference is identity. A normal record is still an object with identity, while a value class is aimed at identity-free value objects. That difference changes how you should think about ==, synchronization, memory layout, and future JVM optimization. So the practical question is not “which one is newer?” but “does this type need identity, or is it really just a small immutable value?”

Table of contents

What Java record solves, and what it does not

Java record was introduced to make data-carrying classes concise. In the past, even a small data type required fields, a constructor, getters, equals, hashCode, and toString.

public record Point(int x, int y) {}

With a record, the compiler generates the basic data-carrier code. That makes records a good fit for API response DTOs, internal command objects, simple configuration values, and test data.

But a record is still a normal object. An object is created on the heap, and object identity still exists. Two records with the same values may be equal by equals(), but they are not necessarily the same object by ==.

Point a = new Point(1, 2);
Point b = new Point(1, 2);

System.out.println(a.equals(b)); // true
System.out.println(a == b);      // false

In that sense, record is closer to a feature that shortens Java code. It does not fundamentally change the object model.

What Project Valhalla value class is trying to change

Project Valhalla’s value class approaches the problem at a lower level. Its goal is to let Java keep object-oriented abstraction while gaining performance characteristics closer to primitives.

The JEP 401 example looks like this.

value record Point(int x, int y) {}

Point p = new Point(17, 3);
Objects.hasIdentity(p);      // false
new Point(17, 3) == p;       // true

The important part is that Objects.hasIdentity(p) is false. This object is less about “a particular object located at a certain heap address” and more about the value itself: x=17, y=3.

Because of that distinction, the JVM can optimize value objects more freely. When possible, it may not need to place a separate object on the heap, and it may be able to lay values out more compactly inside fields or arrays.

The biggest difference between record and value class: identity

A simple comparison looks like this.

record:
- a normal object with identity
- focused on reducing syntax and boilerplate
- automatically generates equals/hashCode/toString
- follows the usual heap-object model

value class:
- an identity-free value object
- focused on improving the JVM object and performance model
- values with the same state can be treated as the same value
- opens the door to optimizations such as flattening and scalarization

This difference is larger than it first appears. If identity exists, an object is closer to “a specific entity at a location” than to “a value that can be copied freely.” Without identity, the JVM can treat it more like a simple bundle of data.

That is why a value class cannot be a synchronization target and may not fit identity-based caches or data structures.

Why value class matters for performance-sensitive Java code

Small objects are created very often in Java code.

Money
Point
Range
UserId
OrderId
EventTime
MetricPoint

These types express domain meaning well. But in performance-sensitive code, many small objects can increase allocation, reference chasing, and GC pressure.

A conventional object array usually looks like this.

Point[]
  -> reference
  -> heap object Point
  -> x, y

If value classes settle into the platform, the JVM can try a more compact layout.

Point[]
  -> x and y values may be placed more directly

This is not only about object creation speed. It also connects to CPU cache locality, GC pressure, and memory usage. One reason Go or Rust can feel strong in performance-sensitive work is that they make data layout easier to reason about. Valhalla is Java’s attempt to reduce that weakness.

Where it may fit in real projects

Once value classes stabilize, the first obvious candidate is the domain value object.

value class Money {
    private long cents;

    public Money(long cents) {
        this.cents = cents;
    }

    public long cents() {
        return cents;
    }
}

Using only long cents may be fast, but the meaning is weak. Using a Money type makes the code safer and easier to read. The problem has been performance. A value class can narrow that gap.

Other candidates include:

  • coordinates, ranges, and time intervals
  • order IDs, user IDs, and trace IDs
  • small value types in events, logs, and metrics
  • small numeric data used in vector search or ranking

Of course, this does not mean every type should become a value class. Objects that need identity, lifecycle, or mutable state still belong as normal classes.

Caveats and quick decision check

A value class is not a drop-in upgrade for record. It is risky to think that every record can simply be replaced with a value class.

Before treating a type as a value-class candidate, check these points first:

  • Does the type need object identity, lifecycle, or mutable state? If yes, keep it as a normal class.
  • Will code use it as a synchronized target, lock key, or identity-based cache key? If yes, value class is the wrong direction.
  • Is it small, immutable, and meaningful mainly by its field values, such as Money, Point, Range, or an ID wrapper? Then it may be a good candidate to evaluate.
  • Are you relying on current production Java? Remember that JEP 401 is still in Preview, so the final specification can change.

In practice, the first candidates should be types that are small, immutable, meaningful as values, and do not require identity. For ordinary DTOs where shorter code is the main goal, record remains the simpler answer.

Conclusion

Record and value class may look similar on the surface because both are used to represent data. But record is a feature that makes Java code more concise, while value class is closer to a change in Java’s object model and performance model.

After using Java for a long time, there are moments when you want a clean domain model but also feel tempted to fall back to primitives for performance. Value class is a possible way to reduce that compromise.

Since it is still in Preview, it is better to watch and test it rather than apply it directly to production code today. Still, as an attempt to keep Java’s object-oriented expressiveness while reducing an old performance weakness, Project Valhalla is a change worth watching.

References

Original Korean version: This article is based on the Korean version and lightly adapted for English readers.
Read the original Korean post.

Please show some love to Korean, too.