diff a/src/java.base/share/classes/java/io/ObjectOutputStream.java b/src/java.base/share/classes/java/io/ObjectOutputStream.java
--- a/src/java.base/share/classes/java/io/ObjectOutputStream.java
+++ b/src/java.base/share/classes/java/io/ObjectOutputStream.java
@@ -37,10 +37,12 @@
 import jdk.internal.util.ByteArray;
 import jdk.internal.access.JavaLangAccess;
 import jdk.internal.access.SharedSecrets;
 import sun.reflect.misc.ReflectUtil;
 
+import static java.io.ObjectInputStream.TRACE;
+
 import static jdk.internal.util.ModifiedUtf.putChar;
 import static jdk.internal.util.ModifiedUtf.utfLen;
 
 /**
  * An ObjectOutputStream writes primitive data types and graphs of Java objects
@@ -131,10 +133,16 @@
  * form.  The methods of the Externalizable interface, writeExternal and
  * readExternal, are called to save and restore the objects state.  When
  * implemented by a class they can write and read their own state using all of
  * the methods of ObjectOutput and ObjectInput.  It is the responsibility of
  * the objects to handle any versioning that occurs.
+ * Value classes implementing {@link Externalizable} cannot be serialized
+ * or deserialized, the value object is immutable and the state cannot be restored.
+ * Use {@link Serializable} {@code writeReplace} to delegate to another serializable
+ * object such as a record.
+ *
+ * Value objects cannot be {@code java.io.Externalizable}.
  *
  * <p>Enum constants are serialized differently than ordinary serializable or
  * externalizable objects.  The serialized form of an enum constant consists
  * solely of its name; field values of the constant are not transmitted.  To
  * serialize an enum constant, ObjectOutputStream writes the string returned by
@@ -156,13 +164,40 @@
  * filled up to 1024 bytes, or be written whenever there is a termination of
  * block-data mode.  Calls to the ObjectOutputStream methods writeObject,
  * defaultWriteObject and writeFields initially terminate any existing
  * block-data record.
  *
+ * <a id="record-serialization"></a>
  * <p>Records are serialized differently than ordinary serializable or externalizable
  * objects, see <a href="ObjectInputStream.html#record-serialization">record serialization</a>.
  *
+ * <a id="valueclass-serialization"></a>
+ * <p>Value classes are {@linkplain Serializable} through the use of the serialization proxy pattern.
+ * The serialization protocol does not support a standard serialized form for value classes.
+ * The value class delegates to a serialization proxy by supplying an alternate
+ * record or object to be serialized instead of the value class.
+ * When the proxy is deserialized it re-constructs the value object and returns the value object.
+ * For example,
+ * {@snippet lang="java" :
+ * value class ZipCode implements Serializable {    // @highlight substring="value class"
+ *     private static final long serialVersionUID = 1L;
+ *     private int zipCode;
+ *     public ZipCode(int zip) { this.zipCode = zip; }
+ *     public int zipCode() { return zipCode; }
+ *
+ *     public Object writeReplace() {    // @highlight substring="writeReplace"
+ *         return new ZipCodeProxy(zipCode);
+ *     }
+ *
+ *     private record ZipCodeProxy(int zipCode) implements Serializable {
+ *         public Object readResolve() {    // @highlight substring="readResolve"
+ *             return new ZipCode(zipCode);
+ *         }
+ *     }
+ * }
+ * }
+ *
  * @spec serialization/index.html Java Object Serialization Specification
  * @author      Mike Warres
  * @author      Roger Riggs
  * @see java.io.DataOutput
  * @see java.io.ObjectInputStream
@@ -343,10 +378,13 @@
      * written.  Default serialization for a class can be overridden using the
      * writeObject and the readObject methods.  Objects referenced by this
      * object are written transitively so that a complete equivalent graph of
      * objects can be reconstructed by an ObjectInputStream.
      *
+     * <p>Serialization and deserialization of value classes is described in
+     * {@linkplain ObjectOutputStream##valueclass-serialization value class serialization}.
+     *
      * <p>Exceptions are thrown for problems with the OutputStream and for
      * classes that should not be serialized.  All exceptions are fatal to the
      * OutputStream, which is left in an indeterminate state, and it is up to
      * the caller to ignore or recover the stream state.
      *
@@ -412,10 +450,13 @@
      * calls to readUnshared by the receiver will not conflict.  Note that the
      * rules described above only apply to the base-level object written with
      * writeUnshared, and not to any transitively referenced sub-objects in the
      * object graph to be serialized.
      *
+     * <p>Serialization and deserialization of value classes is described in
+     * {@linkplain ObjectOutputStream##valueclass-serialization value class serialization}.
+     *
      * <p>ObjectOutputStream subclasses which override this method can only be
      * constructed in security contexts possessing the
      * "enableSubclassImplementation" SerializablePermission; any attempt to
      * instantiate such a subclass without this permission will cause a
      * SecurityException to be thrown.
@@ -1445,10 +1486,13 @@
             handles.assign(unshared ? null : obj);
 
             if (desc.isRecord()) {
                 writeRecordData(obj, desc);
             } else if (desc.isExternalizable() && !desc.isProxy()) {
+                if (desc.isValue())
+                    throw new InvalidClassException("Externalizable not valid for value class "
+                            + desc.forClass().getName());
                 writeExternalData((Externalizable) obj);
             } else {
                 writeSerialData(obj, desc);
             }
         } finally {
@@ -1493,14 +1537,14 @@
     /** Writes the record component values for the given record object. */
     private void writeRecordData(Object obj, ObjectStreamClass desc)
         throws IOException
     {
         assert obj.getClass().isRecord();
-        ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
-        if (slots.length != 1) {
+        List<ObjectStreamClass.ClassDataSlot> slots = desc.getClassDataLayout();
+        if (slots.size() != 1) {
             throw new InvalidClassException(
-                    "expected a single record slot length, but found: " + slots.length);
+                    "expected a single record slot length, but found: " + slots.size());
         }
 
         defaultWriteFields(obj, desc);  // #### seems unnecessary to use the accessors
     }
 
@@ -1509,13 +1553,13 @@
      * superclass to subclass.
      */
     private void writeSerialData(Object obj, ObjectStreamClass desc)
         throws IOException
     {
-        ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
-        for (int i = 0; i < slots.length; i++) {
-            ObjectStreamClass slotDesc = slots[i].desc;
+       List<ObjectStreamClass.ClassDataSlot> slots = desc.getClassDataLayout();
+        for (int i = 0; i < slots.size(); i++) {
+            ObjectStreamClass slotDesc = slots.get(i).desc;
             if (slotDesc.hasWriteObjectMethod()) {
                 PutFieldImpl oldPut = curPut;
                 curPut = null;
                 SerialCallbackContext oldContext = curContext;
 
