Skip to content

8317279: Standard library implementation of value classes and objects#31123

Open
MrSimms wants to merge 2859 commits into
openjdk:masterfrom
MrSimms:jep401_sub_review_8317279
Open

8317279: Standard library implementation of value classes and objects#31123
MrSimms wants to merge 2859 commits into
openjdk:masterfrom
MrSimms:jep401_sub_review_8317279

Conversation

@MrSimms

@MrSimms MrSimms commented May 11, 2026

Copy link
Copy Markdown
Member

This is a "sub-review pull request" for the first preview of JEP 401: Value Classes and Objects, specifically JDK-8317279: Standard library implementation of value classes and objects

Note

This pull request and the other sub-review pull requests listed below are based on the "master pull request" (#31120). It contains the same full set of code changes as the "master pull request" to preserve the full implementation context; the language compiler, JVM, and standard library changes are intertwined. This separate pull requests exist only to subdivide the review and related discussion by area.

Other areas for review:

Code changes resulting from the review process should be made in valhalla/lworld.

valhalla/lworld is currently updated from jdk/master whenever a weekly jdk tag is created. At that time, code changes from valhalla/lworld will be propagated to the master pull request and to all sub-review pull requests, including this one.

Ultimately, review sign-off will be recorded on the "master pull request", and this pull request will be closed without integration.

This pull request has a large code surface area and often conflicts with jdk/master on a daily basis. Refer to valhalla/lworld for the latest state of the project code, keeping in mind that it may lag several days behind jdk/master. Both repositories may be needed as references during review.

PR implementation description

Here's a high-level overview of what's included here.

Core object behaviors

Introduced Objects methods to test for identity and IdentityException for
test failures; revised definitions of == and identityHashCode to work on the
fields of value objects.

  • src/java.base/share/classes/java/lang
  • src/java.base/share/classes/java/util
  • src/java.base/share/classes/java/lang/runtime
  • test/jdk/java/util
  • test/jdk/valhalla/valuetypes
  • test/micro/org/openjdk/bench/valhalla

Value class migration

Generated and compiled a value class version of certain core JDK classes
(primitive wrappers, Optional*, java.time, Number, Record) when preview
features are enabled; mentioned value classes in the documentation for
"value-based class".

  • make/modules/java.base
  • src/java.base/share/classes/java/lang
  • src/java.base/share/classes/java/util
  • src/java.base/share/classes/java/time
  • src/java.base/share/classes/java/lang/doc-files
  • src/java.base/share/classes/jdk/internal/MigratedValueClass.java
  • test/jdk/java/util
  • test/jdk/valhalla/valuetypes/IdentityReflectionTest.java

Preview class deployment

Added tooling support for preview class files stored in a META-INF/preview
folder, to be used when preview features are enabled.

  • make
  • src/java.base/share/classes/jdk/internal/jtrfs
  • src/java.base/share/classes/jdk/internal/module
  • src/jdk.jdeps/share/classes/com/sun/tools/jdeprscan
  • test/jdk/java/lang/module
  • test/jdk/jdk/internal/jimage
  • test/jdk/jdk/internal/jtrfs
  • test/jdk/tools/jlink
  • test/micro/org/openjdk/bench/jdk/internal/jrtfs

New field layouts & behaviors

MethodHandles and VarHandles updated to support new kinds of field and
array layouts, and to support strictly-initialized static field validation. This
includes new lambda forms, etc., to interact with flattened field layouts, which
may or may not be atomic and may or may not permit nulls. (Some layouts are only
possible using internal-only APIs. All layouts share the same descriptor and
reflective Class.)

  • make/modules/java.base
  • src/java.base/share/classes/java/lang/invoke
  • src/java.base/share/classes/java/util/Arrays.java
  • src/java.base/share/classes/jdk/internal/misc
  • src/java.base/share/classes/jdk/internal/reflect
  • src/java.base/share/classes/jdk/internal/value/ValueClass.java
  • src/java.base/share/classes/jdk/internal/vm
  • src/jdk.unsupported
  • test/jdk/java/lang/invoke
  • test/jdk/valhalla/valuetypes
  • test/micro/org/openjdk/bench/valhalla

Core reflection

Added support for the ACC_IDENTITY (sometimes the value language modifier)
and ACC_STRICT_INIT access flags; some changes to AccessFlag to align with
VM handling of class file versions.

  • src/java.base/share/classes/java/lang/Class.java
  • src/java.base/share/classes/java/lang/reflect
  • src/java.base/share/classes/jdk/internal/reflect
  • src/java.base/share/classes/jdk/internal/access
  • test/jdk/java/lang/Class
  • test/jdk/java/lang/reflect

Serialization

Adjusted handling of value classes (and other classes with
strictly-initialized fields) so that, aside from records, they throw an
exception on attempts to serialize/deserialize without
writeReplace/readResolve. Special, private annotation-based mechanism
implemented for the primitive wrapper classes.

  • src/java.base/share/classes/java/io
  • src/java.base/share/classes/java/lang/reflect/ReflectionFactory.java
  • src/java.base/share/classes/jdk/internal/value/DeserializeConstructor.java
  • test/jdk/java/io/Serializable

Reference API

Disallowed value object use with the Reference API and WeakHashMap.

  • src/java.base/share/classes/java/lang/ref
  • src/java.base/share/classes/java/util/WeakHashMap.java
  • src/java.base/share/classes/jdk/internal/util
  • test/jdk/java/lang/ref
  • test/jdk/java/util/WeakHashMapValues.java

ClassFile API

Added support for new ACC_IDENTITY and ACC_STRICT_INIT modifiers; support
for generating early_larval_frames in StackMapTable; and basic support for
the LoadableDescriptors attribute.

  • src/java.base/share/classes/java/lang/classfile
  • src/java.base/share/classes/jdk/internal/classfile
  • test/jdk/jdk/classfile

javap support

Updated to support new class file flags and attributes.

  • src/jdk.jdeps
  • test/langtools/tools/javap

Testing support

Processors for @AsValueClass and @StrictInit testing annotations; improved
support for testing with preview features turned on and off.

  • test/lib
  • test/lib-test
  • test/jtreg_value_class_plugin
  • doc

Language implementation

These can be ignored, they are covered by JDK-8317277:

  • src/java.compiler
  • src/jdk.compiler
  • test/langtools/jdk/javadoc
  • test/langtools/tools/javac
  • test/langtools/tools/lib

JVM implementation

These can be ignored, they are covered by JDK-8317278:

  • .github/workflows
  • src/hotspot
  • src/java.base/share/native
  • src/java.se/share/data/jdwp
  • src/jdk.hotspot.agent
  • src/jdk.jdi
  • src/jdk.jdwp.agent
  • src/jdk.jfr
  • src/utils/IdealGraphVisualizer
  • src/utils/LogCompilation
  • test/hotspot
  • test/jdk/com/sun/jdi
  • test/jdk/java/lang/instrument
  • test/jdk/jdk/internal/vm
  • test/jdk/jdk/jfr

API documentation can be found by clicking the "API" link at https://cr.openjdk.org/~dlsmith/jep401/latest



Progress

  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change requires CSR request JDK-8339199 to be approved
  • Change must be properly reviewed (2 reviews required, with at least 1 Reviewer, 1 Author)

Issues

  • JDK-8317279: Standard library implementation of value classes and objects (Sub-task - P4)
  • JDK-8339199: Standard library implementation of value classes and objects (CSR)

Reviewers

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/31123/head:pull/31123
$ git checkout pull/31123

Update a local copy of the PR:
$ git checkout pull/31123
$ git pull https://git.openjdk.org/jdk.git pull/31123/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 31123

View PR using the GUI difftool:
$ git pr show -t 31123

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/31123.diff

Using Webrev

Link to Webrev Comment

Bill Huang and others added 30 commits April 16, 2026 14:54
Merge jdk-27+17
Reviewed-by: chagedorn, dfenacci
…ing to bad casts with is_inlinetype()

Reviewed-by: thartmann, qamai, dlong
Co-authored-by: Axel Boldt-Christmas <aboldtch@openjdk.org>
Co-authored-by: Joel Sikström <jsikstro@openjdk.org>
Co-authored-by: Stefan Karlsson <stefank@openjdk.org>
Reviewed-by: aboldtch, stefank, thartmann, fparain
…: must constrain OSR typestate

Reviewed-by: thartmann
Merge jdk-27+18
…es.java fails post jdk-27+16

Reviewed-by: vromero
…t001/TestDescription.java SIGSEGV in JvmtiTagMapKey::heapwalk_object

Reviewed-by: sspitsyn
…ithXComp.java#xcomp-disable-tiered-compilation on linux-aarch64

Reviewed-by: dholmes
…ost barrier

Reviewed-by: qamai, thartmann, stefank
…rs for strict fields

Reviewed-by: liach, qamai, thartmann
…llable inline types in the calling convention

Reviewed-by: qamai, chagedorn
…fastdebug

Co-authored-by: Anton Seoane Ampudia <aseoane@openjdk.org>
Reviewed-by: shade, fparain
…t with --enable-preview

Reviewed-by: dholmes, sspitsyn
…y_preload_from_loadable_descriptors

Reviewed-by: matsaave, aboldtch, fparain
…b class

Co-authored-by: Stefan Karlsson <stefank@openjdk.org>
Reviewed-by: phubner, fparain
Reviewed-by: alanb, sgehwolf, thartmann
Merge jdk-27+19
}

/**
* Returns a ModuleFinder that locates modules in a JDK exploded image.
* @param modulesDir the modules directory ($JAVA_HOME/modules)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this is a typo? Should it have been $JAVA_HOME/jmods?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This factory method is for the JDK exploded images, so $JAVA_HOME/modules is correct.

(The $JAVA_HOME/jmods directory is used for packaged modules, typically jlink is run with --keep-packaged-modules to save the packaged modules in that location).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hadn't paid attention that for exploded images the modules directory is named "modules". I just got confused when I saw that name, because in my mind modules meant the runtime image (regular file) that resides in $JAVA_HOME/lib directory.

Thank you for clarifying.

…flat arrays with oops

Reviewed-by: iklam, fparain
return reflectionFactory.parseAccessFlags(getModifiers(),
AccessFlag.Location.METHOD,
getDeclaringClass());
return AccessFlagSet.ofValidated(AccessFlagSet.METHOD_FLAGS, getModifiers());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before this change, the declaring class' class file version was being taken into account to decide the returned AccessFlags. That no longer seems to be the case now and this implementation uses the latest class file version in this decision making. Is that intentional? Would a call to

java.lang.reflect.AccessFlag#maskToAccessFlags(int mask, Location location, ClassFileFormatVersion cffv)

be more appropriate here?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. If you look at hotspot's representation of access_flags, you will realize there is one uniform representation - older flags from previous formats gets translated to that uniform representation in ClassFileParser. And that uniform representation is NOT the latest class file version's maskToAccessFlags: for example, it currently permits useless ACC_STRICT bit for methods to be returned by getModifiers() and accessFlags(), not allowed by the JVMS. This is similar to how later class file versions don't allow jsr/ret but hotspot supports them anyways.

@liach liach Jun 24, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So for each location and preview on/off, there is one uniform way to parse the hotspot representation. Many locations have the same configuration or compatible preview on/off configurations for the same location; in case of compatible configurations, I use the more general flags set, like I use PreviewAccessFlags.FIELD_PREVIEW_FLAGS for Fields, because making another switch to AccessFlagSet.FIELD_FLAGS for preview off accomplishes the same behavior. The only place where the preview on configuraiton is incompatible with the preview off configuration is Class and inner class access flags, which is handled in Class::accessFlags.

@openjdk openjdk Bot added the compiler compiler-dev@openjdk.org label Jun 26, 2026
@openjdk

openjdk Bot commented Jun 26, 2026

Copy link
Copy Markdown

@MrSimms compiler has been added to this pull request based on files touched in new commit(s).

@liach

liach commented Jun 26, 2026

Copy link
Copy Markdown
Member

/labels remove compiler,hotspot,shenandoah

@openjdk

openjdk Bot commented Jun 26, 2026

Copy link
Copy Markdown

@liach Unknown command labels - for a list of valid commands use /help.

@liach

liach commented Jun 26, 2026

Copy link
Copy Markdown
Member

/label remove compiler,hotspot,shenandoah

@openjdk openjdk Bot removed compiler compiler-dev@openjdk.org hotspot hotspot-dev@openjdk.org shenandoah shenandoah-dev@openjdk.org labels Jun 26, 2026
@openjdk

openjdk Bot commented Jun 26, 2026

Copy link
Copy Markdown

@liach
The compiler label was successfully removed.

The hotspot label was successfully removed.

The shenandoah label was successfully removed.


/// {@return whether this field type may store value objects}
/// This excludes primitives and includes Object.
public static boolean isValueObjectCompatible(Class<?> fieldType) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit - some methods on this class use /// and some others use /** style javadoc. Can it be made consistent?


@ParameterizedTest
@MethodSource("valueObjects")
public void roundTrip(Object expected) throws Exception {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this test class and this test method in particular with need some attention. The summary of this test says "Serialize and deserialize value objects". This roundTrip() method sources its param from "valueObjects" method. The valueObjects() method a few lines above constructs instances of SimpleValue class. SimpleValue class (some lines below) is declared as:

public static class SimpleValue implements Serializable

It's an identity class. So, unless I'm missing something, this test method isn't testing anything related to value objects. I initially thought this might be an oversight in the declaration of the SimpleValue class and it probably was missing the value modifier. But this test method expects instances of SimpleValue to be serialized and deserialized without any exceptions, which is contrary to what is being proposed for value objects in context of JEP-401.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ParameterizedTest
@MethodSource("migrationObjects")
public void treeVTest(Object origObj, String origName, String replName, Object expectedObject) throws Exception {
byte[] bytes = serialize(origObj);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test method too I think will need some clean up. The expectedObject passed to this method is never an instance of Exception so the try/catch block in this test method shouldn't exist and the:

@param expectedObject the expected object (graph) or an exception if it should fail

should be updated to remove the mention of the exception.

new SerializablePoint(2, 6)}),
Arguments.of(Arguments.of(
new SerializablePoint(3, 7),
new SerializablePoint(4, 8))),

@jaikiran jaikiran Jun 27, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test used to be testng test which was later converted to junit. It looks like that conversion might have introduced a bug in this parameter generation. Previously, when this test was testng it used to be:

@DataProvider(name = "ImplementSerializable")
public Object[][] implementSerializable() {
    return new Object[][]{
            new Object[]{new SerializablePoint(11, 101)},

            new Object[]{new SerializablePoint[]{
                    new SerializablePoint(1, 5),
                    new SerializablePoint(2, 6)}},

            new Object[]{new Object[]{
                    new SerializablePoint(3, 7),
                    new SerializablePoint(4, 8)}},

            new Object[]{new SerializableFoo(45)},

            new Object[]{new SerializableFoo[]{new SerializableFoo(46)}},

            new Object[]{new ExternalizableFoo("hello")},

            new Object[]{new ExternalizableFoo[]{new ExternalizableFoo("there")}},
    };
}

The conversion has led to:

Arguments.of(Arguments.of(
                        new SerializablePoint(3, 7),
                        new SerializablePoint(4, 8))),

which is wrong. As a result, right now, instead of running the implementSerializable(Object obj) test method 7 times with different paramters, that test method only gets run twice:

[01:38:50.773] STARTED    ValueSerializationTest::implementSerializable '[1] [SerializablePoint x=11 y=101]'
[01:38:50.799] SUCCESSFUL ValueSerializationTest::implementSerializable '[1] [SerializablePoint x=11 y=101]' [25ms]
[01:38:50.802] STARTED    ValueSerializationTest::implementSerializable '[2] [[SerializablePoint x=1 y=5], [SerializablePoint x=2 y=6]]'
[01:38:50.808] SUCCESSFUL ValueSerializationTest::implementSerializable '[2] [[SerializablePoint x=1 y=5], [SerializablePoint x=2 y=6]]' [6ms]

The rest parameters seems to be ignored due to the Arguments.of(Arguments.of(...)) bug. A similar issue exists in the parameters provider for another test method in this class. I'll file an issue and address this shortly.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello Christian @sormuras, would you happen to know if there are ways to catch these issues with parameterized tests by failing the test?

* <li>The class does not have an accessible no-arg constructor
* <li>The ObjectStreamClass of an enum constant does not represent
* an enum type
* <li>A {@linkplain Class#isValue value class} cannot be serialized

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might have to be a bit more precise. In its current form this seems to imply that an instance of a value class cannot be serialized, which isn't accurate because the primitive wrapper classes (for example java.lang.Integer) which are value classes when preview is enabled can be serialized/deserialized. So can instances of value classes like java.time.Instant which implement the writeReplace() method to write out a replacement object to facilitate serialization.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it gets too wordy, then maybe we can just drop the mention of value classes, since there's already a "Other conditions given in the Java Object Serialization Specification" on the next line?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think saying something like "the class is a value class" works - the no-arg scenario doesn't mean no classes without no-arg constructor could be serialized.

@@ -32,6 +32,8 @@ <h1 id="ValueBased">{@index "Value-based Classes"}</h1>

Some classes, such as {@code java.lang.Integer} and
{@code java.time.LocalDate}, are <em>value-based</em>.
The compiler and runtime enforce the value based properties below if it is declared
as {@code value class} and preview features are enabled.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some classes, such as {@code java.lang.Integer} and {@code java.time.LocalDate}, are value-based.
The compiler and runtime enforce the value based properties below if it is declared as {@code value class} ...

Nit, I think that sentence should be "... if they are declared ..."

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the class does not declare (or discourages use of) accessible constructors;

Does this apply for value classes that are being proposed in this JEP? As far as I remember, I haven't seen any text which states or discourages the use of constructors for value object construction.

In fact, on the contrary, in one of the review comments in this PR for a different discussion, it has been rightly pointed out that in recent times there have been efforts to undeprecate previously deprecated-for-removal constructors of the primitive wrapper classes. For example https://bugs.openjdk.org/browse/JDK-8354335.

@@ -49,6 +49,7 @@
* remove entries automatically when the key is garbage collected. This is
* accomplished by using a backing map where the keys are either a
* {@link WeakReference} or a {@link SoftReference}.
* Keys must be {@linkplain Class#isIdentity() identity objects.}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a typo here. Class doesn't have an isIdentity() method in this version of the JEP implementation. I'll file an issue to fix it and to link it to Class.isValue().

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. There are build warning for javadoc link issues like that. If those warnings were disabled, they should be re-enabled before integration.

@jaikiran jaikiran Jun 30, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello Joe, this jdk.internal.util.ReferencedKeyMap is an internal class of java.base module, exported only to select modules. So I suspect these checks aren't run against such classes during the build (I maybe wrong, I haven't checked).

I've filed https://bugs.openjdk.org/browse/JDK-8387495 with a PR available for review openjdk/valhalla#2603

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello Joe, this jdk.internal.util.ReferencedKeyMap is an internal class of java.base module, exported only to select modules. So I suspect these checks aren't run against such classes during the build (I maybe wrong, I haven't checked).

I've filed https://bugs.openjdk.org/browse/JDK-8387495 with a PR available for review openjdk/valhalla#2603

Oh, I overlooked that detail; thanks.

I looking at the makefile changes and all the expected checks for the public types looked to be in place.

@jddarcy

jddarcy commented Jun 30, 2026

Copy link
Copy Markdown
Member

These changes for JEP 401 add preview APIs in a new way compared to prior JEPs. In particular, there are different versions of platform classes with different behavioral semantics with and without preview being enabled. Given that, I think it would be helpful to document the mechanisms used to implement this novel arrangement, say by a new file in the doc directory like "value-class-preview.md".

I'd like to see this file describe:

  • How the preview versions of the sources/classes are generated from the base version, including the build changes.
  • How the wrapper class caches work with preview enabled.
  • High-level test methodology for these classes.

The classes with different version with and without preview enabled could then contain a source-only comment pointing to this file so maintains of those files would know where to go to get more context.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

build build-dev@openjdk.org core-libs core-libs-dev@openjdk.org csr Pull request needs approved CSR before integration i18n i18n-dev@openjdk.org javadoc javadoc-dev@openjdk.org merge-conflict Pull request has merge conflict with target branch net net-dev@openjdk.org nio nio-dev@openjdk.org rfr Pull request is ready for review security security-dev@openjdk.org serviceability serviceability-dev@openjdk.org

Development

Successfully merging this pull request may close these issues.