diff --git a/src/java.base/share/classes/java/lang/constant/ClassDesc.java b/src/java.base/share/classes/java/lang/constant/ClassDesc.java
index b93d9522352..9f58a4f94da 100644
--- a/src/java.base/share/classes/java/lang/constant/ClassDesc.java
+++ b/src/java.base/share/classes/java/lang/constant/ClassDesc.java
@@ -26,22 +26,13 @@ package java.lang.constant;
 
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.TypeDescriptor;
-import java.util.stream.Stream;
 
+import jdk.internal.constant.ArrayClassDescImpl;
+import jdk.internal.constant.ConstantUtils;
 import jdk.internal.constant.PrimitiveClassDescImpl;
-import jdk.internal.constant.ReferenceClassDescImpl;
-import sun.invoke.util.Wrapper;
+import jdk.internal.constant.ClassOrInterfaceDescImpl;
 
-import static java.util.stream.Collectors.joining;
-import static jdk.internal.constant.ConstantUtils.MAX_ARRAY_TYPE_DESC_DIMENSIONS;
-import static jdk.internal.constant.ConstantUtils.arrayDepth;
-import static jdk.internal.constant.ConstantUtils.binaryToInternal;
-import static jdk.internal.constant.ConstantUtils.concat;
-import static jdk.internal.constant.ConstantUtils.forPrimitiveType;
-import static jdk.internal.constant.ConstantUtils.internalToBinary;
-import static jdk.internal.constant.ConstantUtils.validateBinaryClassName;
-import static jdk.internal.constant.ConstantUtils.validateInternalClassName;
-import static jdk.internal.constant.ConstantUtils.validateMemberName;
+import static jdk.internal.constant.ConstantUtils.*;
 
 /**
  * A <a href="package-summary.html#nominal">nominal descriptor</a> for a
@@ -64,7 +55,8 @@ public sealed interface ClassDesc
         extends ConstantDesc,
                 TypeDescriptor.OfField<ClassDesc>
         permits PrimitiveClassDescImpl,
-                ReferenceClassDescImpl {
+                ClassOrInterfaceDescImpl,
+                ArrayClassDescImpl {
 
     /**
      * Returns a {@linkplain ClassDesc} for a class or interface type,
@@ -84,7 +76,7 @@ public sealed interface ClassDesc
      */
     static ClassDesc of(String name) {
         validateBinaryClassName(name);
-        return ClassDesc.ofDescriptor(concat("L", binaryToInternal(name), ";"));
+        return ConstantUtils.binaryNameToDesc(name);
     }
 
     /**
@@ -110,7 +102,7 @@ public sealed interface ClassDesc
      */
     static ClassDesc ofInternalName(String name) {
         validateInternalClassName(name);
-        return ClassDesc.ofDescriptor(concat("L", name, ";"));
+        return ConstantUtils.internalNameToDesc(name);
     }
 
     /**
@@ -129,11 +121,11 @@ public sealed interface ClassDesc
      */
     static ClassDesc of(String packageName, String className) {
         validateBinaryClassName(packageName);
-        if (packageName.isEmpty()) {
-            return of(className);
-        }
         validateMemberName(className, false);
-        return ofDescriptor('L' + binaryToInternal(packageName) +
+        if (packageName.isEmpty()) {
+            return internalNameToDesc(className);
+        }
+        return ClassOrInterfaceDescImpl.ofValidated('L' + binaryToInternal(packageName) +
                 '/' + className + ';');
     }
 
@@ -168,7 +160,7 @@ public sealed interface ClassDesc
         return (descriptor.length() == 1)
                ? forPrimitiveType(descriptor, 0)
                // will throw IAE on descriptor.length == 0 or if array dimensions too long
-               : ReferenceClassDescImpl.of(descriptor);
+               : parseReferenceTypeDesc(descriptor);
     }
 
     /**
@@ -180,20 +172,7 @@ public sealed interface ClassDesc
      * ClassDesc} would have an array rank of greater than 255
      * @jvms 4.4.1 The CONSTANT_Class_info Structure
      */
-    default ClassDesc arrayType() {
-        String desc = descriptorString();
-        int depth = arrayDepth(desc);
-        if (depth >= MAX_ARRAY_TYPE_DESC_DIMENSIONS) {
-            throw new IllegalStateException(
-                    "Cannot create an array type descriptor with more than " +
-                    MAX_ARRAY_TYPE_DESC_DIMENSIONS + " dimensions");
-        }
-        String newDesc = "[".concat(desc);
-        if (desc.length() == 1 && desc.charAt(0) == 'V') {
-            throw new IllegalArgumentException("not a valid reference type descriptor: " + newDesc);
-        }
-        return ReferenceClassDescImpl.ofValidated(newDesc);
-    }
+    ClassDesc arrayType();
 
     /**
      * Returns a {@linkplain ClassDesc} for an array type of the specified rank,
@@ -206,24 +185,7 @@ public sealed interface ClassDesc
      * greater than 255
      * @jvms 4.4.1 The CONSTANT_Class_info Structure
      */
-    default ClassDesc arrayType(int rank) {
-        if (rank <= 0) {
-            throw new IllegalArgumentException("rank " + rank + " is not a positive value");
-        }
-        String desc = descriptorString();
-        long currentDepth = arrayDepth(desc);
-        long netRank = currentDepth + rank;
-        if (netRank > MAX_ARRAY_TYPE_DESC_DIMENSIONS) {
-            throw new IllegalArgumentException("rank: " + netRank +
-                    " exceeds maximum supported dimension of " +
-                    MAX_ARRAY_TYPE_DESC_DIMENSIONS);
-        }
-        String newDesc = new StringBuilder(desc.length() + rank).repeat('[', rank).append(desc).toString();
-        if (desc.length() == 1 && desc.charAt(0) == 'V') {
-            throw new IllegalArgumentException("not a valid reference type descriptor: " + newDesc);
-        }
-        return ReferenceClassDescImpl.ofValidated(newDesc);
-    }
+    ClassDesc arrayType(int rank);
 
     /**
      * Returns a {@linkplain ClassDesc} for a nested class of the class or
@@ -243,13 +205,7 @@ public sealed interface ClassDesc
      * @throws IllegalArgumentException if the nested class name is invalid
      */
     default ClassDesc nested(String nestedName) {
-        validateMemberName(nestedName, false);
-        if (!isClassOrInterface())
-            throw new IllegalStateException("Outer class is not a class or interface type");
-        String desc = descriptorString();
-        StringBuilder sb = new StringBuilder(desc.length() + nestedName.length() + 1);
-        sb.append(desc, 0, desc.length() - 1).append('$').append(nestedName).append(';');
-        return ReferenceClassDescImpl.ofValidated(sb.toString());
+        throw new IllegalStateException("Outer class is not a class or interface type");
     }
 
     /**
@@ -266,16 +222,7 @@ public sealed interface ClassDesc
      * @throws IllegalArgumentException if the nested class name is invalid
      */
     default ClassDesc nested(String firstNestedName, String... moreNestedNames) {
-        if (!isClassOrInterface())
-            throw new IllegalStateException("Outer class is not a class or interface type");
-        validateMemberName(firstNestedName, false);
-        // implicit null-check
-        for (String addNestedNames : moreNestedNames) {
-            validateMemberName(addNestedNames, false);
-        }
-        return moreNestedNames.length == 0
-               ? nested(firstNestedName)
-               : nested(firstNestedName + Stream.of(moreNestedNames).collect(joining("$", "$", "")));
+        throw new IllegalStateException("Outer class is not a class or interface type");
     }
 
     /**
@@ -284,7 +231,7 @@ public sealed interface ClassDesc
      * @return whether this {@linkplain ClassDesc} describes an array type
      */
     default boolean isArray() {
-        return descriptorString().charAt(0) == '[';
+        return false;
     }
 
     /**
@@ -293,7 +240,7 @@ public sealed interface ClassDesc
      * @return whether this {@linkplain ClassDesc} describes a primitive type
      */
     default boolean isPrimitive() {
-        return descriptorString().length() == 1;
+        return false;
     }
 
     /**
@@ -302,7 +249,7 @@ public sealed interface ClassDesc
      * @return whether this {@linkplain ClassDesc} describes a class or interface type
      */
     default boolean isClassOrInterface() {
-        return descriptorString().charAt(0) == 'L';
+        return false;
     }
 
     /**
@@ -313,14 +260,6 @@ public sealed interface ClassDesc
      * if this descriptor does not describe an array type
      */
     default ClassDesc componentType() {
-        if (isArray()) {
-            String desc = descriptorString();
-            if (desc.length() == 2) {
-                return Wrapper.forBasicType(desc.charAt(1)).basicClassDescriptor();
-            } else {
-                return ReferenceClassDescImpl.ofValidated(desc.substring(1));
-            }
-        }
         return null;
     }
 
@@ -332,41 +271,17 @@ public sealed interface ClassDesc
      * default package, or this {@linkplain ClassDesc} does not describe a class or interface type
      */
     default String packageName() {
-        if (!isClassOrInterface())
-            return "";
-        String desc = descriptorString();
-        int index = desc.lastIndexOf('/');
-        return (index == -1) ? "" : internalToBinary(desc.substring(1, index));
+        return "";
     }
 
     /**
-     * Returns a human-readable name for the type described by this descriptor.
-     *
-     * @implSpec
-     * <p>The default implementation returns the simple name
-     * (e.g., {@code int}) for primitive types, the unqualified class name
-     * for class or interface types, or the display name of the component type
-     * suffixed with the appropriate number of {@code []} pairs for array types.
-     *
-     * @return the human-readable name
+     * {@return a human-readable name for this {@code ClassDesc}}
+     * For primitive types, this method returns the simple name (such as {@code int}).
+     * For class or interface types, this method returns the unqualified class name.
+     * For array types, this method returns the human-readable name of the component
+     * type suffixed with the appropriate number of {@code []} pairs.
      */
-    default String displayName() {
-        if (isPrimitive())
-            return Wrapper.forBasicType(descriptorString().charAt(0)).primitiveSimpleName();
-        else if (isClassOrInterface()) {
-            String desc = descriptorString();
-            return desc.substring(Math.max(1, desc.lastIndexOf('/') + 1), desc.length() - 1);
-        }
-        else if (isArray()) {
-            int depth = arrayDepth(descriptorString());
-            ClassDesc c = this;
-            for (int i=0; i<depth; i++)
-                c = c.componentType();
-            return c.displayName().concat("[]".repeat(depth));
-        }
-        else
-            throw new IllegalStateException(descriptorString());
-    }
+    String displayName();
 
     /**
      * Returns a field type descriptor string for this type
diff --git a/src/java.base/share/classes/java/lang/constant/ConstantDescs.java b/src/java.base/share/classes/java/lang/constant/ConstantDescs.java
index 708dfd8fb15..ceb8e023072 100644
--- a/src/java.base/share/classes/java/lang/constant/ConstantDescs.java
+++ b/src/java.base/share/classes/java/lang/constant/ConstantDescs.java
@@ -24,9 +24,10 @@
  */
 package java.lang.constant;
 
+import jdk.internal.constant.ClassOrInterfaceDescImpl;
+import jdk.internal.constant.ConstantUtils;
 import jdk.internal.constant.MethodTypeDescImpl;
 import jdk.internal.constant.PrimitiveClassDescImpl;
-import jdk.internal.constant.ReferenceClassDescImpl;
 
 import java.lang.Enum.EnumDesc;
 import java.lang.invoke.CallSite;
@@ -68,115 +69,120 @@ public final class ConstantDescs {
     // Don't change the order of these declarations!
 
     /** {@link ClassDesc} representing {@link Object} */
-    public static final ClassDesc CD_Object = ReferenceClassDescImpl.ofValidated("Ljava/lang/Object;");
+    public static final ClassDesc CD_Object = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/Object;");
 
     /** {@link ClassDesc} representing {@link String} */
-    public static final ClassDesc CD_String = ReferenceClassDescImpl.ofValidated("Ljava/lang/String;");
+    public static final ClassDesc CD_String = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/String;");
 
     /** {@link ClassDesc} representing {@link Class} */
-    public static final ClassDesc CD_Class = ReferenceClassDescImpl.ofValidated("Ljava/lang/Class;");
+    public static final ClassDesc CD_Class = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/Class;");
 
     /** {@link ClassDesc} representing {@link Number} */
-    public static final ClassDesc CD_Number = ReferenceClassDescImpl.ofValidated("Ljava/lang/Number;");
+    public static final ClassDesc CD_Number = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/Number;");
 
     /** {@link ClassDesc} representing {@link Integer} */
-    public static final ClassDesc CD_Integer = ReferenceClassDescImpl.ofValidated("Ljava/lang/Integer;");
+    public static final ClassDesc CD_Integer = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/Integer;");
 
     /** {@link ClassDesc} representing {@link Long} */
-    public static final ClassDesc CD_Long = ReferenceClassDescImpl.ofValidated("Ljava/lang/Long;");
+    public static final ClassDesc CD_Long = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/Long;");
 
     /** {@link ClassDesc} representing {@link Float} */
-    public static final ClassDesc CD_Float = ReferenceClassDescImpl.ofValidated("Ljava/lang/Float;");
+    public static final ClassDesc CD_Float = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/Float;");
 
     /** {@link ClassDesc} representing {@link Double} */
-    public static final ClassDesc CD_Double = ReferenceClassDescImpl.ofValidated("Ljava/lang/Double;");
+    public static final ClassDesc CD_Double = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/Double;");
 
     /** {@link ClassDesc} representing {@link Short} */
-    public static final ClassDesc CD_Short = ReferenceClassDescImpl.ofValidated("Ljava/lang/Short;");
+    public static final ClassDesc CD_Short = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/Short;");
 
     /** {@link ClassDesc} representing {@link Byte} */
-    public static final ClassDesc CD_Byte = ReferenceClassDescImpl.ofValidated("Ljava/lang/Byte;");
+    public static final ClassDesc CD_Byte = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/Byte;");
 
     /** {@link ClassDesc} representing {@link Character} */
-    public static final ClassDesc CD_Character = ReferenceClassDescImpl.ofValidated("Ljava/lang/Character;");
+    public static final ClassDesc CD_Character = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/Character;");
 
     /** {@link ClassDesc} representing {@link Boolean} */
-    public static final ClassDesc CD_Boolean = ReferenceClassDescImpl.ofValidated("Ljava/lang/Boolean;");
+    public static final ClassDesc CD_Boolean = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/Boolean;");
 
     /** {@link ClassDesc} representing {@link Void} */
-    public static final ClassDesc CD_Void = ReferenceClassDescImpl.ofValidated("Ljava/lang/Void;");
+    public static final ClassDesc CD_Void = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/Void;");
 
     /** {@link ClassDesc} representing {@link Throwable} */
-    public static final ClassDesc CD_Throwable = ReferenceClassDescImpl.ofValidated("Ljava/lang/Throwable;");
+    public static final ClassDesc CD_Throwable = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/Throwable;");
 
     /** {@link ClassDesc} representing {@link Exception} */
-    public static final ClassDesc CD_Exception = ReferenceClassDescImpl.ofValidated("Ljava/lang/Exception;");
+    public static final ClassDesc CD_Exception = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/Exception;");
 
     /** {@link ClassDesc} representing {@link Enum} */
-    public static final ClassDesc CD_Enum = ReferenceClassDescImpl.ofValidated("Ljava/lang/Enum;");
+    public static final ClassDesc CD_Enum = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/Enum;");
 
     /** {@link ClassDesc} representing {@link VarHandle} */
-    public static final ClassDesc CD_VarHandle = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/VarHandle;");
+    public static final ClassDesc CD_VarHandle = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/invoke/VarHandle;");
 
     /** {@link ClassDesc} representing {@link MethodHandles} */
-    public static final ClassDesc CD_MethodHandles = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/MethodHandles;");
+    public static final ClassDesc CD_MethodHandles = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/invoke/MethodHandles;");
 
     /** {@link ClassDesc} representing {@link MethodHandles.Lookup} */
-    public static final ClassDesc CD_MethodHandles_Lookup = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/MethodHandles$Lookup;");
+    public static final ClassDesc CD_MethodHandles_Lookup = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/invoke/MethodHandles$Lookup;");
 
     /** {@link ClassDesc} representing {@link MethodHandle} */
-    public static final ClassDesc CD_MethodHandle = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/MethodHandle;");
+    public static final ClassDesc CD_MethodHandle = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/invoke/MethodHandle;");
 
     /** {@link ClassDesc} representing {@link MethodType} */
-    public static final ClassDesc CD_MethodType = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/MethodType;");
+    public static final ClassDesc CD_MethodType = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/invoke/MethodType;");
 
     /** {@link ClassDesc} representing {@link CallSite} */
-    public static final ClassDesc CD_CallSite = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/CallSite;");
+    public static final ClassDesc CD_CallSite = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/invoke/CallSite;");
 
     /** {@link ClassDesc} representing {@link Collection} */
-    public static final ClassDesc CD_Collection = ReferenceClassDescImpl.ofValidated("Ljava/util/Collection;");
+    public static final ClassDesc CD_Collection = ClassOrInterfaceDescImpl.ofValidated("Ljava/util/Collection;");
 
     /** {@link ClassDesc} representing {@link List} */
-    public static final ClassDesc CD_List = ReferenceClassDescImpl.ofValidated("Ljava/util/List;");
+    public static final ClassDesc CD_List = ClassOrInterfaceDescImpl.ofValidated("Ljava/util/List;");
 
     /** {@link ClassDesc} representing {@link Set} */
-    public static final ClassDesc CD_Set = ReferenceClassDescImpl.ofValidated("Ljava/util/Set;");
+    public static final ClassDesc CD_Set = ClassOrInterfaceDescImpl.ofValidated("Ljava/util/Set;");
 
     /** {@link ClassDesc} representing {@link Map} */
-    public static final ClassDesc CD_Map = ReferenceClassDescImpl.ofValidated("Ljava/util/Map;");
+    public static final ClassDesc CD_Map = ClassOrInterfaceDescImpl.ofValidated("Ljava/util/Map;");
 
     /** {@link ClassDesc} representing {@link ConstantDesc} */
-    public static final ClassDesc CD_ConstantDesc = ReferenceClassDescImpl.ofValidated("Ljava/lang/constant/ConstantDesc;");
+    public static final ClassDesc CD_ConstantDesc = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/constant/ConstantDesc;");
 
     /** {@link ClassDesc} representing {@link ClassDesc} */
-    public static final ClassDesc CD_ClassDesc = ReferenceClassDescImpl.ofValidated("Ljava/lang/constant/ClassDesc;");
+    public static final ClassDesc CD_ClassDesc = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/constant/ClassDesc;");
 
     /** {@link ClassDesc} representing {@link EnumDesc} */
-    public static final ClassDesc CD_EnumDesc = ReferenceClassDescImpl.ofValidated("Ljava/lang/Enum$EnumDesc;");
+    public static final ClassDesc CD_EnumDesc = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/Enum$EnumDesc;");
 
     /** {@link ClassDesc} representing {@link MethodTypeDesc} */
-    public static final ClassDesc CD_MethodTypeDesc = ReferenceClassDescImpl.ofValidated("Ljava/lang/constant/MethodTypeDesc;");
+    public static final ClassDesc CD_MethodTypeDesc = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/constant/MethodTypeDesc;");
 
     /** {@link ClassDesc} representing {@link MethodHandleDesc} */
-    public static final ClassDesc CD_MethodHandleDesc = ReferenceClassDescImpl.ofValidated("Ljava/lang/constant/MethodHandleDesc;");
+    public static final ClassDesc CD_MethodHandleDesc = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/constant/MethodHandleDesc;");
 
     /** {@link ClassDesc} representing {@link DirectMethodHandleDesc} */
-    public static final ClassDesc CD_DirectMethodHandleDesc = ReferenceClassDescImpl.ofValidated("Ljava/lang/constant/DirectMethodHandleDesc;");
+    public static final ClassDesc CD_DirectMethodHandleDesc = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/constant/DirectMethodHandleDesc;");
 
     /** {@link ClassDesc} representing {@link VarHandleDesc} */
-    public static final ClassDesc CD_VarHandleDesc = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/VarHandle$VarHandleDesc;");
+    public static final ClassDesc CD_VarHandleDesc = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/invoke/VarHandle$VarHandleDesc;");
 
     /** {@link ClassDesc} representing {@link DirectMethodHandleDesc.Kind} */
-    public static final ClassDesc CD_MethodHandleDesc_Kind = ReferenceClassDescImpl.ofValidated("Ljava/lang/constant/DirectMethodHandleDesc$Kind;");
+    public static final ClassDesc CD_MethodHandleDesc_Kind = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/constant/DirectMethodHandleDesc$Kind;");
 
     /** {@link ClassDesc} representing {@link DynamicConstantDesc} */
-    public static final ClassDesc CD_DynamicConstantDesc = ReferenceClassDescImpl.ofValidated("Ljava/lang/constant/DynamicConstantDesc;");
+    public static final ClassDesc CD_DynamicConstantDesc = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/constant/DynamicConstantDesc;");
 
     /** {@link ClassDesc} representing {@link DynamicCallSiteDesc} */
-    public static final ClassDesc CD_DynamicCallSiteDesc = ReferenceClassDescImpl.ofValidated("Ljava/lang/constant/DynamicCallSiteDesc;");
+    public static final ClassDesc CD_DynamicCallSiteDesc = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/constant/DynamicCallSiteDesc;");
 
     /** {@link ClassDesc} representing {@link ConstantBootstraps} */
-    public static final ClassDesc CD_ConstantBootstraps = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/ConstantBootstraps;");
+    public static final ClassDesc CD_ConstantBootstraps = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/invoke/ConstantBootstraps;");
+
+    static {
+        // avoid circular initialization
+        ConstantUtils.CD_Object_array = CD_Object.arrayType();
+    }
 
     private static final ClassDesc[] INDY_BOOTSTRAP_ARGS = {
             CD_MethodHandles_Lookup,
@@ -229,7 +235,7 @@ public final class ConstantDescs {
     /** {@link MethodHandleDesc} representing {@link ConstantBootstraps#invoke(Lookup, String, Class, MethodHandle, Object...) ConstantBootstraps.invoke} */
     public static final DirectMethodHandleDesc BSM_INVOKE
             = ofConstantBootstrap(CD_ConstantBootstraps, "invoke",
-            CD_Object, CD_MethodHandle, CD_Object.arrayType());
+            CD_Object, CD_MethodHandle, ConstantUtils.CD_Object_array);
 
     /**
      * {@link MethodHandleDesc} representing {@link ConstantBootstraps#explicitCast(Lookup, String, Class, Object) ConstantBootstraps.explicitCast}
diff --git a/src/java.base/share/classes/java/lang/invoke/ClassSpecializer.java b/src/java.base/share/classes/java/lang/invoke/ClassSpecializer.java
index 94a271780c3..1ed7c422c98 100644
--- a/src/java.base/share/classes/java/lang/invoke/ClassSpecializer.java
+++ b/src/java.base/share/classes/java/lang/invoke/ClassSpecializer.java
@@ -42,8 +42,9 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
+import jdk.internal.constant.ClassOrInterfaceDescImpl;
+import jdk.internal.constant.ConstantUtils;
 import jdk.internal.constant.MethodTypeDescImpl;
-import jdk.internal.constant.ReferenceClassDescImpl;
 import jdk.internal.loader.BootLoader;
 import jdk.internal.vm.annotation.Stable;
 import sun.invoke.util.BytecodeName;
@@ -65,8 +66,8 @@ import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
 /*non-public*/
 abstract class ClassSpecializer<T,K,S extends ClassSpecializer<T,K,S>.SpeciesData> {
 
-    private static final ClassDesc CD_LambdaForm = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/LambdaForm;");
-    private static final ClassDesc CD_BoundMethodHandle = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/BoundMethodHandle;");
+    private static final ClassDesc CD_LambdaForm = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/invoke/LambdaForm;");
+    private static final ClassDesc CD_BoundMethodHandle = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/invoke/BoundMethodHandle;");
 
     private final Class<T> topClass;
     private final Class<K> keyType;
@@ -974,7 +975,7 @@ abstract class ClassSpecializer<T,K,S extends ClassSpecializer<T,K,S>.SpeciesDat
              : cls == MethodType.class ? CD_MethodType
              : cls == LambdaForm.class ? CD_LambdaForm
              : cls == BoundMethodHandle.class ? CD_BoundMethodHandle
-             : ReferenceClassDescImpl.ofValidated(cls.descriptorString());
+             : ConstantUtils.referenceClassDesc(cls.descriptorString());
     }
 
     static MethodTypeDesc methodDesc(MethodType mt) {
diff --git a/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java b/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java
index 10f282065fd..93784004994 100644
--- a/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java
+++ b/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java
@@ -25,6 +25,7 @@
 
 package java.lang.invoke;
 
+import jdk.internal.constant.ClassOrInterfaceDescImpl;
 import jdk.internal.misc.CDS;
 import jdk.internal.util.ClassFileDumper;
 import sun.invoke.util.VerifyAccess;
@@ -53,10 +54,8 @@ import java.lang.classfile.constantpool.ConstantPoolBuilder;
 import static java.lang.constant.ConstantDescs.*;
 import static java.lang.invoke.MethodHandleNatives.Constants.NESTMATE_CLASS;
 import static java.lang.invoke.MethodHandleNatives.Constants.STRONG_LOADER_LINK;
-import static java.lang.invoke.MethodType.methodType;
 import jdk.internal.constant.ConstantUtils;
 import jdk.internal.constant.MethodTypeDescImpl;
-import jdk.internal.constant.ReferenceClassDescImpl;
 import jdk.internal.vm.annotation.Stable;
 import sun.invoke.util.Wrapper;
 
@@ -157,7 +156,7 @@ import sun.invoke.util.Wrapper;
         implMethodDesc = methodDesc(implInfo.getMethodType());
         constructorType = factoryType.changeReturnType(Void.TYPE);
         lambdaClassName = lambdaClassName(targetClass);
-        lambdaClassEntry = pool.classEntry(ReferenceClassDescImpl.ofValidated(ConstantUtils.concat("L", lambdaClassName, ";")));
+        lambdaClassEntry = pool.classEntry(ConstantUtils.internalNameToDesc(lambdaClassName));
         // If the target class invokes a protected method inherited from a
         // superclass in a different package, or does 'invokespecial', the
         // lambda class has no access to the resolved method, or does
@@ -407,9 +406,9 @@ import sun.invoke.util.Wrapper;
 
     private static class SerializationSupport {
         // Serialization support
-        private static final ClassDesc CD_SerializedLambda = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/SerializedLambda;");
-        private static final ClassDesc CD_ObjectOutputStream = ReferenceClassDescImpl.ofValidated("Ljava/io/ObjectOutputStream;");
-        private static final ClassDesc CD_ObjectInputStream = ReferenceClassDescImpl.ofValidated("Ljava/io/ObjectInputStream;");
+        private static final ClassDesc CD_SerializedLambda = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/invoke/SerializedLambda;");
+        private static final ClassDesc CD_ObjectOutputStream = ClassOrInterfaceDescImpl.ofValidated("Ljava/io/ObjectOutputStream;");
+        private static final ClassDesc CD_ObjectInputStream = ClassOrInterfaceDescImpl.ofValidated("Ljava/io/ObjectInputStream;");
         private static final MethodTypeDesc MTD_Object = MethodTypeDescImpl.ofValidated(CD_Object);
         private static final MethodTypeDesc MTD_void_ObjectOutputStream = MethodTypeDescImpl.ofValidated(CD_void, CD_ObjectOutputStream);
         private static final MethodTypeDesc MTD_void_ObjectInputStream = MethodTypeDescImpl.ofValidated(CD_void, CD_ObjectInputStream);
@@ -418,10 +417,10 @@ import sun.invoke.util.Wrapper;
         private static final String NAME_METHOD_READ_OBJECT = "readObject";
         private static final String NAME_METHOD_WRITE_OBJECT = "writeObject";
 
-        static final ClassDesc CD_NotSerializableException = ReferenceClassDescImpl.ofValidated("Ljava/io/NotSerializableException;");
+        static final ClassDesc CD_NotSerializableException = ClassOrInterfaceDescImpl.ofValidated("Ljava/io/NotSerializableException;");
         static final MethodTypeDesc MTD_CTOR_NOT_SERIALIZABLE_EXCEPTION = MethodTypeDescImpl.ofValidated(CD_void, CD_String);
         static final MethodTypeDesc MTD_CTOR_SERIALIZED_LAMBDA = MethodTypeDescImpl.ofValidated(CD_void,
-                CD_Class, CD_String, CD_String, CD_String, CD_int, CD_String, CD_String, CD_String, CD_String, ReferenceClassDescImpl.ofValidated("[Ljava/lang/Object;"));
+                CD_Class, CD_String, CD_String, CD_String, CD_int, CD_String, CD_String, CD_String, CD_String, ConstantUtils.CD_Object_array);
 
     }
 
@@ -557,12 +556,12 @@ import sun.invoke.util.Wrapper;
     }
 
     static ClassDesc implClassDesc(Class<?> cls) {
-        return cls.isHidden() ? null : ReferenceClassDescImpl.ofValidated(cls.descriptorString());
+        return cls.isHidden() ? null : ConstantUtils.referenceClassDesc(cls.descriptorString());
     }
 
     static ClassDesc classDesc(Class<?> cls) {
         return cls.isPrimitive() ? Wrapper.forPrimitiveType(cls).basicClassDescriptor()
-                                 : ReferenceClassDescImpl.ofValidated(cls.descriptorString());
+                                 : ConstantUtils.referenceClassDesc(cls.descriptorString());
     }
 
     static MethodTypeDesc methodDesc(MethodType mt) {
diff --git a/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java b/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java
index 5d307a9ff04..ec131d67f2b 100644
--- a/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java
+++ b/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java
@@ -25,6 +25,8 @@
 
 package java.lang.invoke;
 
+import jdk.internal.constant.ClassOrInterfaceDescImpl;
+import jdk.internal.constant.ConstantUtils;
 import sun.invoke.util.VerifyAccess;
 import sun.invoke.util.VerifyType;
 import sun.invoke.util.Wrapper;
@@ -50,7 +52,6 @@ import java.util.List;
 import java.util.function.Consumer;
 import java.util.stream.Stream;
 import jdk.internal.constant.MethodTypeDescImpl;
-import jdk.internal.constant.ReferenceClassDescImpl;
 
 import static java.lang.classfile.ClassFile.*;
 import static java.lang.constant.ConstantDescs.*;
@@ -59,7 +60,6 @@ import static java.lang.invoke.LambdaForm.BasicType.*;
 import static java.lang.invoke.MethodHandleNatives.Constants.*;
 import static java.lang.invoke.MethodHandleStatics.*;
 import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
-import static jdk.internal.constant.ConstantUtils.concat;
 import static jdk.internal.constant.ConstantUtils.validateInternalClassName;
 
 /**
@@ -69,16 +69,16 @@ import static jdk.internal.constant.ConstantUtils.validateInternalClassName;
  */
 class InvokerBytecodeGenerator {
     /** Define class names for convenience. */
-    private static final ClassDesc CD_CasesHolder = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/MethodHandleImpl$CasesHolder;");
-    private static final ClassDesc CD_DirectMethodHandle = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/DirectMethodHandle;");
-    private static final ClassDesc CD_MemberName = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/MemberName;");
-    private static final ClassDesc CD_MethodHandleImpl = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/MethodHandleImpl;");
-    private static final ClassDesc CD_LambdaForm = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/LambdaForm;");
-    private static final ClassDesc CD_LambdaForm_Name = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/LambdaForm$Name;");
-    private static final ClassDesc CD_LoopClauses = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/MethodHandleImpl$LoopClauses;");
-    private static final ClassDesc CD_Object_array  = ReferenceClassDescImpl.ofValidated("[Ljava/lang/Object;");
-    private static final ClassDesc CD_MethodHandle_array = ReferenceClassDescImpl.ofValidated("[Ljava/lang/invoke/MethodHandle;");
-    private static final ClassDesc CD_MethodHandle_array2 = ReferenceClassDescImpl.ofValidated("[[Ljava/lang/invoke/MethodHandle;");
+    private static final ClassDesc CD_CasesHolder = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/invoke/MethodHandleImpl$CasesHolder;");
+    private static final ClassDesc CD_DirectMethodHandle = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/invoke/DirectMethodHandle;");
+    private static final ClassDesc CD_MemberName = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/invoke/MemberName;");
+    private static final ClassDesc CD_MethodHandleImpl = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/invoke/MethodHandleImpl;");
+    private static final ClassDesc CD_LambdaForm = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/invoke/LambdaForm;");
+    private static final ClassDesc CD_LambdaForm_Name = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/invoke/LambdaForm$Name;");
+    private static final ClassDesc CD_LoopClauses = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/invoke/MethodHandleImpl$LoopClauses;");
+    private static final ClassDesc CD_Object_array = ConstantUtils.CD_Object_array;
+    private static final ClassDesc CD_MethodHandle_array = CD_MethodHandle.arrayType();
+    private static final ClassDesc CD_MethodHandle_array2 = CD_MethodHandle_array.arrayType();
 
     private static final MethodTypeDesc MTD_boolean_Object = MethodTypeDescImpl.ofValidated(CD_boolean, CD_Object);
     private static final MethodTypeDesc MTD_Object_int = MethodTypeDescImpl.ofValidated(CD_Object, CD_int);
@@ -133,7 +133,7 @@ class InvokerBytecodeGenerator {
         this.name = name;
         this.className = CLASS_PREFIX.concat(name);
         validateInternalClassName(name);
-        this.classEntry = pool.classEntry(ReferenceClassDescImpl.ofValidated(concat("L", className, ";")));
+        this.classEntry = pool.classEntry(ConstantUtils.internalNameToDesc(className));
         this.lambdaForm = lambdaForm;
         this.invokerName = invokerName;
         this.invokerType = invokerType;
@@ -517,11 +517,11 @@ class InvokerBytecodeGenerator {
         return true;
     }
 
-    static final Annotation DONTINLINE      = Annotation.of(ReferenceClassDescImpl.ofValidated("Ljdk/internal/vm/annotation/DontInline;"));
-    static final Annotation FORCEINLINE     = Annotation.of(ReferenceClassDescImpl.ofValidated("Ljdk/internal/vm/annotation/ForceInline;"));
-    static final Annotation HIDDEN          = Annotation.of(ReferenceClassDescImpl.ofValidated("Ljdk/internal/vm/annotation/Hidden;"));
-    static final Annotation INJECTEDPROFILE = Annotation.of(ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/InjectedProfile;"));
-    static final Annotation LF_COMPILED     = Annotation.of(ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/LambdaForm$Compiled;"));
+    static final Annotation DONTINLINE      = Annotation.of(ClassOrInterfaceDescImpl.ofValidated("Ljdk/internal/vm/annotation/DontInline;"));
+    static final Annotation FORCEINLINE     = Annotation.of(ClassOrInterfaceDescImpl.ofValidated("Ljdk/internal/vm/annotation/ForceInline;"));
+    static final Annotation HIDDEN          = Annotation.of(ClassOrInterfaceDescImpl.ofValidated("Ljdk/internal/vm/annotation/Hidden;"));
+    static final Annotation INJECTEDPROFILE = Annotation.of(ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/invoke/InjectedProfile;"));
+    static final Annotation LF_COMPILED     = Annotation.of(ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/invoke/LambdaForm$Compiled;"));
 
     // Suppress method in backtraces displayed to the user, mark this method as
     // a compiled LambdaForm, then either force or prohibit inlining.
@@ -1649,7 +1649,7 @@ class InvokerBytecodeGenerator {
              : cls == MemberName.class ? CD_MemberName
              : cls == MethodType.class ? CD_MethodType
              : cls.isPrimitive() ? Wrapper.forPrimitiveType(cls).basicClassDescriptor()
-             : ReferenceClassDescImpl.ofValidated(cls.descriptorString());
+             : ConstantUtils.referenceClassDesc(cls.descriptorString());
     }
 
     static MethodTypeDesc methodDesc(MethodType mt) {
diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java b/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java
index 992ef387684..fd3e20a524e 100644
--- a/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java
+++ b/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java
@@ -27,8 +27,9 @@ package java.lang.invoke;
 
 import jdk.internal.access.JavaLangInvokeAccess;
 import jdk.internal.access.SharedSecrets;
+import jdk.internal.constant.ClassOrInterfaceDescImpl;
+import jdk.internal.constant.ConstantUtils;
 import jdk.internal.constant.MethodTypeDescImpl;
-import jdk.internal.constant.ReferenceClassDescImpl;
 import jdk.internal.foreign.abi.NativeEntryPoint;
 import jdk.internal.reflect.CallerSensitive;
 import jdk.internal.reflect.Reflection;
@@ -54,7 +55,6 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Function;
 import java.util.stream.Stream;
@@ -67,7 +67,6 @@ import static java.lang.invoke.MethodHandleNatives.Constants.MN_HIDDEN_MEMBER;
 import static java.lang.invoke.MethodHandleNatives.Constants.NESTMATE_CLASS;
 import static java.lang.invoke.MethodHandleStatics.*;
 import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
-import static java.lang.invoke.MethodHandles.Lookup.ClassOption.NESTMATE;
 
 /**
  * Trusted implementation code for MethodHandle.
@@ -1038,7 +1037,7 @@ abstract class MethodHandleImpl {
     // That way we can lazily load the code and set up the constants.
     private static class BindCaller {
 
-        private static final ClassDesc CD_Object_array = ReferenceClassDescImpl.ofValidated("[Ljava/lang/Object;");
+        private static final ClassDesc CD_Object_array = ConstantUtils.CD_Object_array;
         private static final MethodType INVOKER_MT = MethodType.methodType(Object.class, MethodHandle.class, Object[].class);
         private static final MethodType REFLECT_INVOKER_MT = MethodType.methodType(Object.class, MethodHandle.class, Object.class, Object[].class);
 
@@ -1267,7 +1266,7 @@ abstract class MethodHandleImpl {
             //     }
             // }
             // }
-            return ClassFile.of().build(ReferenceClassDescImpl.ofValidated("LInjectedInvoker;"), clb -> clb
+            return ClassFile.of().build(ClassOrInterfaceDescImpl.ofValidated("LInjectedInvoker;"), clb -> clb
                     .withFlags(ACC_PRIVATE | ACC_SUPER)
                     .withMethodBody(
                         "invoke_V",
diff --git a/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java b/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java
index 97848e1fcb3..cca16a18580 100644
--- a/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java
+++ b/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java
@@ -28,9 +28,9 @@ package java.lang.invoke;
 
 import jdk.internal.access.JavaLangAccess;
 import jdk.internal.access.SharedSecrets;
+import jdk.internal.constant.ClassOrInterfaceDescImpl;
 import jdk.internal.constant.ConstantUtils;
 import jdk.internal.constant.MethodTypeDescImpl;
-import jdk.internal.constant.ReferenceClassDescImpl;
 import jdk.internal.misc.VM;
 import jdk.internal.util.ClassFileDumper;
 import jdk.internal.util.ReferenceKey;
@@ -1088,10 +1088,10 @@ public final class StringConcatFactory {
         static final MethodHandles.Lookup STR_LOOKUP = new MethodHandles.Lookup(String.class);
 
         static final ClassDesc CD_CONCAT             = ConstantUtils.binaryNameToDesc(CLASS_NAME);
-        static final ClassDesc CD_StringConcatHelper = ReferenceClassDescImpl.ofValidated("Ljava/lang/StringConcatHelper;");
-        static final ClassDesc CD_StringConcatBase   = ReferenceClassDescImpl.ofValidated("Ljava/lang/StringConcatHelper$StringConcatBase;");
-        static final ClassDesc CD_Array_byte         = ReferenceClassDescImpl.ofValidated("[B");
-        static final ClassDesc CD_Array_String       = ReferenceClassDescImpl.ofValidated("[Ljava/lang/String;");
+        static final ClassDesc CD_StringConcatHelper = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/StringConcatHelper;");
+        static final ClassDesc CD_StringConcatBase   = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/StringConcatHelper$StringConcatBase;");
+        static final ClassDesc CD_Array_byte         = CD_byte.arrayType();
+        static final ClassDesc CD_Array_String       = CD_String.arrayType();
 
         static final MethodTypeDesc MTD_byte_char       = MethodTypeDescImpl.ofValidated(CD_byte, CD_char);
         static final MethodTypeDesc MTD_byte            = MethodTypeDescImpl.ofValidated(CD_byte);
diff --git a/src/java.base/share/classes/java/lang/invoke/TypeConvertingMethodAdapter.java b/src/java.base/share/classes/java/lang/invoke/TypeConvertingMethodAdapter.java
index 83fefe07d51..f372e053260 100644
--- a/src/java.base/share/classes/java/lang/invoke/TypeConvertingMethodAdapter.java
+++ b/src/java.base/share/classes/java/lang/invoke/TypeConvertingMethodAdapter.java
@@ -30,8 +30,9 @@ import java.lang.classfile.CodeBuilder;
 import java.lang.classfile.TypeKind;
 import java.lang.classfile.constantpool.ConstantPoolBuilder;
 import java.lang.classfile.constantpool.MethodRefEntry;
+
+import jdk.internal.constant.ConstantUtils;
 import jdk.internal.constant.MethodTypeDescImpl;
-import jdk.internal.constant.ReferenceClassDescImpl;
 import sun.invoke.util.Wrapper;
 
 import static java.lang.constant.ConstantDescs.*;
@@ -202,6 +203,6 @@ class TypeConvertingMethodAdapter {
         return cls.isPrimitive() ? Wrapper.forPrimitiveType(cls).basicClassDescriptor()
              : cls == Object.class ? CD_Object
              : cls == String.class ? CD_String
-             : ReferenceClassDescImpl.ofValidated(cls.descriptorString());
+             : ConstantUtils.referenceClassDesc(cls.descriptorString());
     }
 }
diff --git a/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java b/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java
index abdcaf5ae1f..efbb3919ef5 100644
--- a/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java
+++ b/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java
@@ -39,9 +39,10 @@ import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
 import java.util.Objects;
+
+import jdk.internal.constant.ClassOrInterfaceDescImpl;
 import jdk.internal.constant.ConstantUtils;
 import jdk.internal.constant.MethodTypeDescImpl;
-import jdk.internal.constant.ReferenceClassDescImpl;
 import sun.security.action.GetBooleanAction;
 
 import static java.lang.classfile.ClassFile.*;
@@ -64,18 +65,18 @@ final class ProxyGenerator {
             ClassFile.of(ClassFile.StackMapsOption.DROP_STACK_MAPS);
 
     private static final ClassDesc
-            CD_ClassLoader = ReferenceClassDescImpl.ofValidated("Ljava/lang/ClassLoader;"),
-            CD_Class_array = ReferenceClassDescImpl.ofValidated("[Ljava/lang/Class;"),
-            CD_ClassNotFoundException = ReferenceClassDescImpl.ofValidated("Ljava/lang/ClassNotFoundException;"),
-            CD_NoClassDefFoundError = ReferenceClassDescImpl.ofValidated("Ljava/lang/NoClassDefFoundError;"),
-            CD_IllegalAccessException = ReferenceClassDescImpl.ofValidated("Ljava/lang/IllegalAccessException;"),
-            CD_InvocationHandler = ReferenceClassDescImpl.ofValidated("Ljava/lang/reflect/InvocationHandler;"),
-            CD_Method = ReferenceClassDescImpl.ofValidated("Ljava/lang/reflect/Method;"),
-            CD_NoSuchMethodError = ReferenceClassDescImpl.ofValidated("Ljava/lang/NoSuchMethodError;"),
-            CD_NoSuchMethodException = ReferenceClassDescImpl.ofValidated("Ljava/lang/NoSuchMethodException;"),
-            CD_Object_array = ReferenceClassDescImpl.ofValidated("[Ljava/lang/Object;"),
-            CD_Proxy = ReferenceClassDescImpl.ofValidated("Ljava/lang/reflect/Proxy;"),
-            CD_UndeclaredThrowableException = ReferenceClassDescImpl.ofValidated("Ljava/lang/reflect/UndeclaredThrowableException;");
+            CD_ClassLoader = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/ClassLoader;"),
+            CD_Class_array = CD_Class.arrayType(),
+            CD_ClassNotFoundException = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/ClassNotFoundException;"),
+            CD_NoClassDefFoundError = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/NoClassDefFoundError;"),
+            CD_IllegalAccessException = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/IllegalAccessException;"),
+            CD_InvocationHandler = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/reflect/InvocationHandler;"),
+            CD_Method = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/reflect/Method;"),
+            CD_NoSuchMethodError = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/NoSuchMethodError;"),
+            CD_NoSuchMethodException = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/NoSuchMethodException;"),
+            CD_Object_array = ConstantUtils.CD_Object_array,
+            CD_Proxy = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/reflect/Proxy;"),
+            CD_UndeclaredThrowableException = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/reflect/UndeclaredThrowableException;");
 
     private static final MethodTypeDesc
             MTD_boolean = MethodTypeDescImpl.ofValidated(CD_boolean),
diff --git a/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java b/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java
index 0c0144b24db..53f1572fa74 100644
--- a/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java
+++ b/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java
@@ -48,9 +48,9 @@ import java.lang.classfile.ClassFile;
 import java.lang.classfile.Label;
 import java.lang.classfile.instruction.SwitchCase;
 
+import jdk.internal.constant.ClassOrInterfaceDescImpl;
 import jdk.internal.constant.ConstantUtils;
 import jdk.internal.constant.MethodTypeDescImpl;
-import jdk.internal.constant.ReferenceClassDescImpl;
 import jdk.internal.misc.PreviewFeatures;
 import jdk.internal.vm.annotation.Stable;
 
@@ -82,8 +82,8 @@ public class SwitchBootstraps {
     private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
     private static final boolean previewEnabled = PreviewFeatures.isEnabled();
 
-    private static final ClassDesc CD_BiPredicate = ReferenceClassDescImpl.ofValidated("Ljava/util/function/BiPredicate;");
-    private static final ClassDesc CD_Objects = ReferenceClassDescImpl.ofValidated("Ljava/util/Objects;");
+    private static final ClassDesc CD_BiPredicate = ClassOrInterfaceDescImpl.ofValidated("Ljava/util/function/BiPredicate;");
+    private static final ClassDesc CD_Objects = ClassOrInterfaceDescImpl.ofValidated("Ljava/util/Objects;");
 
     private static final MethodTypeDesc CHECK_INDEX_DESCRIPTOR =
             MethodTypeDescImpl.ofValidated(CD_int, CD_int, CD_int);
@@ -584,7 +584,7 @@ public class SwitchBootstraps {
 
                             TypePairs typePair = TypePairs.of(Wrapper.asPrimitiveType(selectorType), classLabel);
                             String methodName = TypePairs.typePairToName.get(typePair);
-                            cb.invokestatic(referenceClassDesc(ExactConversionsSupport.class),
+                            cb.invokestatic(ConstantUtils.referenceClassDesc(ExactConversionsSupport.class),
                                     methodName,
                                     MethodTypeDesc.of(CD_boolean, classDesc(typePair.from)))
                               .ifeq(next);
diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/SplitConstantPool.java b/src/java.base/share/classes/jdk/internal/classfile/impl/SplitConstantPool.java
index f0e0047f57d..0e26a8941e0 100644
--- a/src/java.base/share/classes/jdk/internal/classfile/impl/SplitConstantPool.java
+++ b/src/java.base/share/classes/jdk/internal/classfile/impl/SplitConstantPool.java
@@ -34,8 +34,6 @@ import java.lang.constant.MethodTypeDesc;
 import java.util.Arrays;
 import java.util.List;
 
-import jdk.internal.constant.ConstantUtils;
-
 import static java.lang.classfile.constantpool.PoolEntry.*;
 import static java.util.Objects.requireNonNull;
 
diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapGenerator.java b/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapGenerator.java
index 85ee77b1720..d4bff1bfe80 100644
--- a/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapGenerator.java
+++ b/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapGenerator.java
@@ -43,11 +43,10 @@ import java.util.List;
 import java.util.Objects;
 import java.util.stream.Collectors;
 
-import jdk.internal.constant.PrimitiveClassDescImpl;
-import jdk.internal.constant.ReferenceClassDescImpl;
+import jdk.internal.constant.ClassOrInterfaceDescImpl;
 import jdk.internal.util.Preconditions;
 
-import static java.lang.classfile.ClassFile.ACC_STATIC;
+import static java.lang.classfile.ClassFile.*;
 import static java.lang.classfile.constantpool.PoolEntry.*;
 import static java.lang.constant.ConstantDescs.*;
 import static jdk.internal.classfile.impl.RawBytecodeHelper.*;
@@ -1059,7 +1058,7 @@ public final class StackMapGenerator {
             if (desc == CD_double) return pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
             return desc == CD_void ? this
                     : pushStack(
-                    desc instanceof PrimitiveClassDescImpl
+                    desc.isPrimitive()
                             ? (desc == CD_float ? Type.FLOAT_TYPE : Type.INTEGER_TYPE)
                             : Type.referenceType(desc));
         }
@@ -1069,7 +1068,7 @@ public final class StackMapGenerator {
             if (desc == CD_double) return decStack1PushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE);
             return desc == CD_void ? this
                     : decStack1PushStack(
-                    desc instanceof PrimitiveClassDescImpl
+                    desc.isPrimitive()
                             ? (desc == CD_float ? Type.FLOAT_TYPE : Type.INTEGER_TYPE)
                             : Type.referenceType(desc));
         }
@@ -1274,7 +1273,7 @@ public final class StackMapGenerator {
                     locals[localsSize + 1] = Type.DOUBLE2_TYPE;
                     localsSize += 2;
                 } else {
-                    if (desc instanceof ReferenceClassDescImpl) {
+                    if (!desc.isPrimitive()) {
                         type = Type.referenceType(desc);
                     } else if (desc == CD_float) {
                         type = Type.FLOAT_TYPE;
@@ -1459,14 +1458,14 @@ public final class StackMapGenerator {
         //frequently used types to reduce footprint
         static final Type OBJECT_TYPE = referenceType(CD_Object),
             THROWABLE_TYPE = referenceType(CD_Throwable),
-            INT_ARRAY_TYPE = referenceType(ReferenceClassDescImpl.ofValidated("[I")),
-            BOOLEAN_ARRAY_TYPE = referenceType(ReferenceClassDescImpl.ofValidated("[Z")),
-            BYTE_ARRAY_TYPE = referenceType(ReferenceClassDescImpl.ofValidated("[B")),
-            CHAR_ARRAY_TYPE = referenceType(ReferenceClassDescImpl.ofValidated("[C")),
-            SHORT_ARRAY_TYPE = referenceType(ReferenceClassDescImpl.ofValidated("[S")),
-            LONG_ARRAY_TYPE = referenceType(ReferenceClassDescImpl.ofValidated("[J")),
-            DOUBLE_ARRAY_TYPE = referenceType(ReferenceClassDescImpl.ofValidated("[D")),
-            FLOAT_ARRAY_TYPE = referenceType(ReferenceClassDescImpl.ofValidated("[F")),
+            INT_ARRAY_TYPE = referenceType(CD_int.arrayType()),
+            BOOLEAN_ARRAY_TYPE = referenceType(CD_boolean.arrayType()),
+            BYTE_ARRAY_TYPE = referenceType(CD_byte.arrayType()),
+            CHAR_ARRAY_TYPE = referenceType(CD_char.arrayType()),
+            SHORT_ARRAY_TYPE = referenceType(CD_short.arrayType()),
+            LONG_ARRAY_TYPE = referenceType(CD_long.arrayType()),
+            DOUBLE_ARRAY_TYPE = referenceType(CD_double.arrayType()),
+            FLOAT_ARRAY_TYPE = referenceType(CD_float.arrayType()),
             STRING_TYPE = referenceType(CD_String),
             CLASS_TYPE = referenceType(CD_Class),
             METHOD_HANDLE_TYPE = referenceType(CD_MethodHandle),
@@ -1531,8 +1530,8 @@ public final class StackMapGenerator {
             }
         }
 
-        private static final ClassDesc CD_Cloneable = ReferenceClassDescImpl.ofValidated("Ljava/lang/Cloneable;");
-        private static final ClassDesc CD_Serializable = ReferenceClassDescImpl.ofValidated("Ljava/io/Serializable;");
+        private static final ClassDesc CD_Cloneable = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/Cloneable;");
+        private static final ClassDesc CD_Serializable = ClassOrInterfaceDescImpl.ofValidated("Ljava/io/Serializable;");
 
         private Type mergeReferenceFrom(Type from, ClassHierarchyImpl context) {
             if (from == NULL_TYPE) {
diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/Util.java b/src/java.base/share/classes/jdk/internal/classfile/impl/Util.java
index 848d153c24b..1088724d8b4 100644
--- a/src/java.base/share/classes/jdk/internal/classfile/impl/Util.java
+++ b/src/java.base/share/classes/jdk/internal/classfile/impl/Util.java
@@ -42,7 +42,7 @@ import java.util.function.Consumer;
 import java.util.function.Function;
 
 import jdk.internal.access.SharedSecrets;
-import jdk.internal.constant.ReferenceClassDescImpl;
+import jdk.internal.constant.ClassOrInterfaceDescImpl;
 import jdk.internal.vm.annotation.ForceInline;
 import jdk.internal.vm.annotation.Stable;
 
@@ -134,8 +134,8 @@ public class Util {
     }
 
     public static String toInternalName(ClassDesc cd) {
-        if (cd instanceof ReferenceClassDescImpl rcd) {
-            return rcd.internalName();
+        if (cd instanceof ClassOrInterfaceDescImpl coi) {
+            return coi.internalName();
         }
         throw new IllegalArgumentException(cd.descriptorString());
     }
diff --git a/src/java.base/share/classes/jdk/internal/constant/ArrayClassDescImpl.java b/src/java.base/share/classes/jdk/internal/constant/ArrayClassDescImpl.java
new file mode 100644
index 00000000000..763975cb76b
--- /dev/null
+++ b/src/java.base/share/classes/jdk/internal/constant/ArrayClassDescImpl.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.internal.constant;
+
+import java.lang.constant.ClassDesc;
+import java.lang.invoke.MethodHandles;
+
+import jdk.internal.vm.annotation.Stable;
+import sun.invoke.util.Wrapper;
+
+import static java.lang.constant.ConstantDescs.CD_void;
+import static jdk.internal.constant.ConstantUtils.MAX_ARRAY_TYPE_DESC_DIMENSIONS;
+
+/**
+ * An array class descriptor.
+ * Restrictions: <ul>
+ * <li>{@code rank} must be in {@code [1, 255]}
+ * <li>{@code element} must not be void or array
+ * </ul>
+ */
+public final class ArrayClassDescImpl implements ClassDesc {
+    private final ClassDesc elementType;
+    private final int rank;
+    private @Stable String cachedDescriptorString;
+
+    public static ArrayClassDescImpl ofValidatedDescriptor(String desc) {
+        assert desc.charAt(0) == '[';
+        var lastChar = desc.charAt(desc.length() - 1);
+        ArrayClassDescImpl ret;
+        if (lastChar != ';') {
+            // Primitive element arrays
+            ret = ofValidated(Wrapper.forBasicType(lastChar).basicClassDescriptor(), desc.length() - 1);
+        } else {
+            int level = ConstantUtils.arrayDepth(desc, 0);
+            ret = ofValidated(ClassOrInterfaceDescImpl.ofValidated(desc.substring(level)), level);
+        }
+        ret.cachedDescriptorString = desc;
+        return ret;
+    }
+
+    public static ArrayClassDescImpl ofValidated(ClassDesc elementType, int rank) {
+        assert !elementType.isArray() && elementType != CD_void;
+        assert rank > 0 && rank <= MAX_ARRAY_TYPE_DESC_DIMENSIONS;
+
+        return new ArrayClassDescImpl(elementType, rank);
+    }
+
+    private ArrayClassDescImpl(ClassDesc elementType, int rank) {
+        this.elementType = elementType;
+        this.rank = rank;
+    }
+
+    @Override
+    public ClassDesc arrayType() {
+        int rank = this.rank + 1;
+        if (rank > MAX_ARRAY_TYPE_DESC_DIMENSIONS)
+            throw new IllegalStateException(ConstantUtils.invalidArrayRankMessage(rank));
+        return new ArrayClassDescImpl(elementType, rank);
+    }
+
+    @Override
+    public ClassDesc arrayType(int rank) {
+        if (rank <= 0) {
+            throw new IllegalArgumentException("rank " + rank + " is not a positive value");
+        }
+        rank += this.rank;
+        ConstantUtils.validateArrayRank(rank);
+        return new ArrayClassDescImpl(elementType, rank);
+    }
+
+    @Override
+    public boolean isArray() {
+        return true;
+    }
+
+    @Override
+    public ClassDesc componentType() {
+        return rank == 1 ? elementType : new ArrayClassDescImpl(elementType, rank - 1);
+    }
+
+    @Override
+    public String displayName() {
+        return elementType.displayName() + "[]".repeat(rank);
+    }
+
+    @Override
+    public String descriptorString() {
+        var desc = cachedDescriptorString;
+        if (desc != null)
+            return desc;
+
+        return cachedDescriptorString = computeDescriptor();
+    }
+
+    private String computeDescriptor() {
+        var componentDesc = elementType.descriptorString();
+        StringBuilder sb = new StringBuilder(rank + componentDesc.length());
+        sb.repeat('[', rank);
+        sb.append(componentDesc);
+        return sb.toString();
+    }
+
+    @Override
+    public Class<?> resolveConstantDesc(MethodHandles.Lookup lookup) throws ReflectiveOperationException {
+        if (elementType.isPrimitive()) {
+            return lookup.findClass(descriptorString());
+        }
+        // Class.forName is slow on class or interface arrays
+        Class<?> clazz = elementType.resolveConstantDesc(lookup);
+        for (int i = 0; i < rank; i++)
+            clazz = clazz.arrayType();
+        return clazz;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o instanceof ArrayClassDescImpl constant) {
+            return elementType.equals(constant.elementType) && rank == constant.rank;
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return descriptorString().hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("ArrayClassDesc[%s, %d]", elementType.displayName(), rank);
+    }
+}
diff --git a/src/java.base/share/classes/jdk/internal/constant/ClassOrInterfaceDescImpl.java b/src/java.base/share/classes/jdk/internal/constant/ClassOrInterfaceDescImpl.java
new file mode 100644
index 00000000000..dcdb2cff8b5
--- /dev/null
+++ b/src/java.base/share/classes/jdk/internal/constant/ClassOrInterfaceDescImpl.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.internal.constant;
+
+import java.lang.constant.ClassDesc;
+import java.lang.invoke.MethodHandles;
+
+import jdk.internal.vm.annotation.Stable;
+
+import static jdk.internal.constant.ConstantUtils.*;
+
+/**
+ * A class or interface descriptor.
+ * Restrictions:
+ * <ul>
+ * <li>Starts with 'L'
+ * <li>Ends with ';'
+ * <li>No '.' or '[' or ';' in the middle
+ * <li>No leading/trailing/consecutive '/'
+ * </ul>
+ */
+public final class ClassOrInterfaceDescImpl implements ClassDesc {
+    private final String descriptor;
+    private @Stable String internalName;
+
+    /**
+     * Creates a {@linkplain ClassOrInterfaceDescImpl} from a pre-validated descriptor string
+     * for a class or interface.
+     */
+    public static ClassOrInterfaceDescImpl ofValidated(String descriptor) {
+        assert ConstantUtils.skipOverFieldSignature(descriptor, 0, descriptor.length())
+                == descriptor.length() : descriptor;
+        assert descriptor.charAt(0) == 'L';
+        return new ClassOrInterfaceDescImpl(descriptor);
+    }
+
+    ClassOrInterfaceDescImpl(String descriptor) {
+        this.descriptor = descriptor;
+    }
+
+    public String internalName() {
+        var internalName = this.internalName;
+        if (internalName == null) {
+            this.internalName = internalName = dropFirstAndLastChar(descriptor);
+        }
+        return internalName;
+    }
+
+    @Override
+    public ClassDesc arrayType(int rank) {
+        ConstantUtils.validateArrayRank(rank);
+        return ArrayClassDescImpl.ofValidated(this, rank);
+    }
+
+    @Override
+    public ClassDesc arrayType() {
+        return ArrayClassDescImpl.ofValidated(this, 1);
+    }
+
+    @Override
+    public ClassDesc nested(String nestedName) {
+        validateMemberName(nestedName, false);
+        String desc = descriptorString();
+        StringBuilder sb = new StringBuilder(desc.length() + nestedName.length() + 1);
+        sb.append(desc, 0, desc.length() - 1).append('$').append(nestedName).append(';');
+        return ofValidated(sb.toString());
+    }
+
+    @Override
+    public ClassDesc nested(String firstNestedName, String... moreNestedNames) {
+        validateMemberName(firstNestedName, false);
+        // implicit null-check
+        for (String addNestedNames : moreNestedNames) {
+            validateMemberName(addNestedNames, false);
+        }
+        return moreNestedNames.length == 0
+                ? nested(firstNestedName)
+                : nested(firstNestedName + "$" + String.join("$", moreNestedNames));
+
+    }
+
+    @Override
+    public boolean isClassOrInterface() {
+        return true;
+    }
+
+    @Override
+    public String packageName() {
+        String desc = descriptorString();
+        int index = desc.lastIndexOf('/');
+        return (index == -1) ? "" : internalToBinary(desc.substring(1, index));
+    }
+
+    @Override
+    public String displayName() {
+        String desc = descriptorString();
+        return desc.substring(Math.max(1, desc.lastIndexOf('/') + 1), desc.length() - 1);
+    }
+
+    @Override
+    public String descriptorString() {
+        return descriptor;
+    }
+
+    @Override
+    public Class<?> resolveConstantDesc(MethodHandles.Lookup lookup)
+            throws ReflectiveOperationException {
+        return lookup.findClass(internalToBinary(internalName()));
+    }
+
+    /**
+     * Returns {@code true} if this {@linkplain ClassOrInterfaceDescImpl} is
+     * equal to another {@linkplain ClassOrInterfaceDescImpl}.  Equality is
+     * determined by the two class descriptors having equal class descriptor
+     * strings.
+     *
+     * @param o the {@code ClassDesc} to compare to this
+     *       {@code ClassDesc}
+     * @return {@code true} if the specified {@code ClassDesc}
+     *      is equal to this {@code ClassDesc}.
+     */
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o instanceof ClassOrInterfaceDescImpl constant) {
+            return descriptor.equals(constant.descriptor);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return descriptor.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("ClassOrInterfaceDesc[%s]", displayName());
+    }
+}
diff --git a/src/java.base/share/classes/jdk/internal/constant/ConstantUtils.java b/src/java.base/share/classes/jdk/internal/constant/ConstantUtils.java
index 5640f849ab6..21791937649 100644
--- a/src/java.base/share/classes/jdk/internal/constant/ConstantUtils.java
+++ b/src/java.base/share/classes/jdk/internal/constant/ConstantUtils.java
@@ -24,6 +24,7 @@
  */
 package jdk.internal.constant;
 
+import jdk.internal.vm.annotation.Stable;
 import sun.invoke.util.Wrapper;
 
 import java.lang.constant.ClassDesc;
@@ -31,8 +32,6 @@ import java.lang.constant.ConstantDesc;
 import java.lang.constant.ConstantDescs;
 import java.lang.constant.MethodTypeDesc;
 import java.lang.invoke.MethodType;
-import java.util.ArrayList;
-import java.util.List;
 import java.util.Set;
 
 import jdk.internal.access.JavaLangAccess;
@@ -50,6 +49,7 @@ public final class ConstantUtils {
     public static final ClassDesc[] EMPTY_CLASSDESC = new ClassDesc[0];
     public static final int MAX_ARRAY_TYPE_DESC_DIMENSIONS = 255;
     public static final ClassDesc CD_module_info = binaryNameToDesc("module-info");
+    public static @Stable ClassDesc CD_Object_array; // set from ConstantDescs, avoid circular initialization
 
     private static final Set<String> pointyNames = Set.of(ConstantDescs.INIT_NAME, ConstantDescs.CLASS_INIT_NAME);
 
@@ -70,7 +70,18 @@ public final class ConstantUtils {
      * @param binaryName a binary name
      */
     public static ClassDesc binaryNameToDesc(String binaryName) {
-        return ReferenceClassDescImpl.ofValidated(concat("L", binaryToInternal(binaryName), ";"));
+        return internalNameToDesc(binaryToInternal(binaryName));
+    }
+
+    /**
+     * Creates a {@linkplain ClassDesc} from a pre-validated internal name
+     * for a class or interface type. Validated version of {@link
+     * ClassDesc#ofInternalName(String)}.
+     *
+     * @param internalName a binary name
+     */
+    public static ClassDesc internalNameToDesc(String internalName) {
+        return ClassOrInterfaceDescImpl.ofValidated(concat("L", internalName, ";"));
     }
 
     /**
@@ -91,7 +102,21 @@ public final class ConstantUtils {
      * class or interface or an array type with a non-hidden component type.
      */
     public static ClassDesc referenceClassDesc(Class<?> type) {
-        return ReferenceClassDescImpl.ofValidated(type.descriptorString());
+        return referenceClassDesc(type.descriptorString());
+    }
+
+    /**
+     * Creates a {@linkplain ClassDesc} from a pre-validated descriptor string
+     * for a class or interface type or an array type.
+     *
+     * @param descriptor a field descriptor string for a class or interface type
+     * @jvms 4.3.2 Field Descriptors
+     */
+    public static ClassDesc referenceClassDesc(String descriptor) {
+        if (descriptor.charAt(0) == '[') {
+            return ArrayClassDescImpl.ofValidatedDescriptor(descriptor);
+        }
+        return ClassOrInterfaceDescImpl.ofValidated(descriptor);
     }
 
     /**
@@ -128,6 +153,26 @@ public final class ConstantUtils {
         return MethodTypeDescImpl.ofValidated(returnDesc, paramDescs);
     }
 
+    /**
+     * Creates a {@linkplain ClassDesc} from a descriptor string for a class or
+     * interface type or an array type.
+     *
+     * @param descriptor a field descriptor string for a class or interface type
+     * @throws IllegalArgumentException if the descriptor string is not a valid
+     * field descriptor string, or does not describe a class or interface type
+     * @jvms 4.3.2 Field Descriptors
+     */
+    public static ClassDesc parseReferenceTypeDesc(String descriptor) {
+        int dLen = descriptor.length();
+        int len = ConstantUtils.skipOverFieldSignature(descriptor, 0, dLen);
+        if (len <= 1 || len != dLen)
+            throw new IllegalArgumentException(String.format("not a valid reference type descriptor: %s", descriptor));
+        if (descriptor.charAt(0) == '[') {
+            return ArrayClassDescImpl.ofValidatedDescriptor(descriptor);
+        }
+        return ClassOrInterfaceDescImpl.ofValidated(descriptor);
+    }
+
     /**
      * Validates the correctness of a binary class name. In particular checks for the presence of
      * invalid characters in the name.
@@ -140,8 +185,9 @@ public final class ConstantUtils {
     public static String validateBinaryClassName(String name) {
         for (int i = 0; i < name.length(); i++) {
             char ch = name.charAt(i);
-            if (ch == ';' || ch == '[' || ch == '/')
-                throw new IllegalArgumentException("Invalid class name: " + name);
+            if (ch == ';' || ch == '[' || ch == '/'
+                    || ch == '.' && (i == 0 || i + 1 == name.length() || name.charAt(i - 1) == '.'))
+                throw invalidClassName(name);
         }
         return name;
     }
@@ -158,8 +204,9 @@ public final class ConstantUtils {
     public static String validateInternalClassName(String name) {
         for (int i = 0; i < name.length(); i++) {
             char ch = name.charAt(i);
-            if (ch == ';' || ch == '[' || ch == '.')
-                throw new IllegalArgumentException("Invalid class name: " + name);
+            if (ch == ';' || ch == '[' || ch == '.'
+                    || ch == '/' && (i == 0 || i + 1 == name.length() || name.charAt(i - 1) == '/'))
+                throw invalidClassName(name);
         }
         return name;
     }
@@ -256,10 +303,24 @@ public final class ConstantUtils {
             throw new IllegalArgumentException("not a class or interface type: " + classDesc);
     }
 
-    public static int arrayDepth(String descriptorString) {
+    public static void validateArrayRank(int rank) {
+        // array rank must be representable with u1 and nonzero
+        if (rank == 0 || (rank & ~0xFF) != 0) {
+            throw new IllegalArgumentException(invalidArrayRankMessage(rank));
+        }
+    }
+
+    /**
+     * Retrieves the array depth on a trusted descriptor.
+     * Uses a simple loop with the assumption that most descriptors have
+     * 0 or very low array depths.
+     */
+    public static int arrayDepth(String descriptorString, int off) {
         int depth = 0;
-        while (descriptorString.charAt(depth) == '[')
+        while (descriptorString.charAt(off) == '[') {
             depth++;
+            off++;
+        }
         return depth;
     }
 
@@ -296,7 +357,22 @@ public final class ConstantUtils {
         }
 
         // Pre-verified in MethodTypeDescImpl#ofDescriptor; avoid redundant verification
-        return ReferenceClassDescImpl.ofValidated(descriptor.substring(start, start + len));
+        int arrayDepth = arrayDepth(descriptor, start);
+        if (arrayDepth == 0) {
+            return ClassOrInterfaceDescImpl.ofValidated(descriptor.substring(start, start + len));
+        } else if (arrayDepth + 1 == len) {
+            return ArrayClassDescImpl.ofValidated(forPrimitiveType(descriptor, start + arrayDepth), arrayDepth);
+        } else {
+            return ArrayClassDescImpl.ofValidated(ClassOrInterfaceDescImpl.ofValidated(descriptor.substring(start + arrayDepth, start + len)), arrayDepth);
+        }
+    }
+
+    static String invalidArrayRankMessage(int rank) {
+        return "Array rank must be within [1, 255]: " + rank;
+    }
+
+    static IllegalArgumentException invalidClassName(String className) {
+        return new IllegalArgumentException("Invalid class name: ".concat(className));
     }
 
     static IllegalArgumentException badMethodDescriptor(String descriptor) {
diff --git a/src/java.base/share/classes/jdk/internal/constant/MethodTypeDescImpl.java b/src/java.base/share/classes/jdk/internal/constant/MethodTypeDescImpl.java
index 6f59c476a6b..826fc095bbd 100644
--- a/src/java.base/share/classes/jdk/internal/constant/MethodTypeDescImpl.java
+++ b/src/java.base/share/classes/jdk/internal/constant/MethodTypeDescImpl.java
@@ -39,13 +39,13 @@ import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
 
+import static java.lang.constant.ConstantDescs.CD_void;
 import static java.util.Objects.requireNonNull;
 
 import static jdk.internal.constant.ConstantUtils.badMethodDescriptor;
 import static jdk.internal.constant.ConstantUtils.resolveClassDesc;
 import static jdk.internal.constant.ConstantUtils.skipOverFieldSignature;
 import static jdk.internal.constant.ConstantUtils.EMPTY_CLASSDESC;
-import static jdk.internal.constant.PrimitiveClassDescImpl.CD_void;
 
 /**
  * A <a href="package-summary.html#nominal">nominal descriptor</a> for a
@@ -86,7 +86,7 @@ public final class MethodTypeDescImpl implements MethodTypeDesc {
     }
 
     private static ClassDesc validateArgument(ClassDesc arg) {
-        if (arg.descriptorString().charAt(0) == 'V') // implicit null check
+        if (requireNonNull(arg) == CD_void)
             throw new IllegalArgumentException("Void parameters not permitted");
         return arg;
     }
diff --git a/src/java.base/share/classes/jdk/internal/constant/PrimitiveClassDescImpl.java b/src/java.base/share/classes/jdk/internal/constant/PrimitiveClassDescImpl.java
index 35aa4007816..31ad92a2736 100644
--- a/src/java.base/share/classes/jdk/internal/constant/PrimitiveClassDescImpl.java
+++ b/src/java.base/share/classes/jdk/internal/constant/PrimitiveClassDescImpl.java
@@ -93,6 +93,31 @@ public final class PrimitiveClassDescImpl
         return this.lazyWrapper = Wrapper.forBasicType(descriptorString().charAt(0));
     }
 
+    @Override
+    public boolean isPrimitive() {
+        return true;
+    }
+
+    @Override
+    public ClassDesc arrayType(int rank) {
+        ConstantUtils.validateArrayRank(rank);
+        if (this == CD_void)
+            throw new IllegalArgumentException("not a valid reference type descriptor: " + "[".repeat(rank) + "V");
+        return ArrayClassDescImpl.ofValidated(this, rank);
+    }
+
+    @Override
+    public ClassDesc arrayType() {
+        if (this == CD_void)
+            throw new IllegalArgumentException("not a valid reference type descriptor: [V");
+        return ArrayClassDescImpl.ofValidated(this, 1);
+    }
+
+    @Override
+    public String displayName() {
+        return wrapper().primitiveSimpleName();
+    }
+
     @Override
     public String descriptorString() {
         return descriptor;
diff --git a/src/java.base/share/classes/jdk/internal/constant/ReferenceClassDescImpl.java b/src/java.base/share/classes/jdk/internal/constant/ReferenceClassDescImpl.java
deleted file mode 100644
index 8124abc6b75..00000000000
--- a/src/java.base/share/classes/jdk/internal/constant/ReferenceClassDescImpl.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.internal.constant;
-
-import java.lang.constant.ClassDesc;
-import java.lang.invoke.MethodHandles;
-
-import jdk.internal.vm.annotation.Stable;
-import static jdk.internal.constant.ConstantUtils.*;
-
-/**
- * A <a href="package-summary.html#nominal">nominal descriptor</a> for a class,
- * interface, or array type.  A {@linkplain ReferenceClassDescImpl} corresponds to a
- * {@code Constant_Class_info} entry in the constant pool of a classfile.
- */
-public final class ReferenceClassDescImpl implements ClassDesc {
-    private final String descriptor;
-    private @Stable String internalName;
-
-    private ReferenceClassDescImpl(String descriptor) {
-        this.descriptor = descriptor;
-    }
-
-    /**
-     * Creates a {@linkplain ClassDesc} from a descriptor string for a class or
-     * interface type or an array type.
-     *
-     * @param descriptor a field descriptor string for a class or interface type
-     * @throws IllegalArgumentException if the descriptor string is not a valid
-     * field descriptor string, or does not describe a class or interface type
-     * @jvms 4.3.2 Field Descriptors
-     */
-    public static ReferenceClassDescImpl of(String descriptor) {
-        int dLen = descriptor.length();
-        int len = ConstantUtils.skipOverFieldSignature(descriptor, 0, dLen);
-        if (len <= 1 || len != dLen)
-            throw new IllegalArgumentException(String.format("not a valid reference type descriptor: %s", descriptor));
-        return new ReferenceClassDescImpl(descriptor);
-    }
-
-    /**
-     * Creates a {@linkplain ClassDesc} from a pre-validated descriptor string
-     * for a class or interface type or an array type.
-     *
-     * @param descriptor a field descriptor string for a class or interface type
-     * @jvms 4.3.2 Field Descriptors
-     */
-    public static ReferenceClassDescImpl ofValidated(String descriptor) {
-        assert ConstantUtils.skipOverFieldSignature(descriptor, 0, descriptor.length())
-                == descriptor.length() : descriptor;
-        return new ReferenceClassDescImpl(descriptor);
-    }
-
-    @Override
-    public String descriptorString() {
-        return descriptor;
-    }
-
-    public String internalName() {
-        var internalName = this.internalName;
-        if (internalName == null) {
-            var desc = this.descriptor;
-            this.internalName = internalName = desc.charAt(0) == 'L' ? dropFirstAndLastChar(desc) : desc;
-        }
-
-        return internalName;
-    }
-
-    @Override
-    public Class<?> resolveConstantDesc(MethodHandles.Lookup lookup)
-            throws ReflectiveOperationException {
-        if (isArray()) {
-            if (isPrimitiveArray()) {
-                return lookup.findClass(descriptor);
-            }
-            // Class.forName is slow on class or interface arrays
-            int depth = ConstantUtils.arrayDepth(descriptor);
-            Class<?> clazz = lookup.findClass(internalToBinary(descriptor.substring(depth + 1, descriptor.length() - 1)));
-            for (int i = 0; i < depth; i++)
-                clazz = clazz.arrayType();
-            return clazz;
-        }
-        return lookup.findClass(internalToBinary(internalName()));
-    }
-
-    /**
-     * Whether the descriptor is one of a primitive array, given this is
-     * already a valid reference type descriptor.
-     */
-    private boolean isPrimitiveArray() {
-        // All L-type descriptors must end with a semicolon; same for reference
-        // arrays, leaving primitive arrays the only ones without a final semicolon
-        return descriptor.charAt(descriptor.length() - 1) != ';';
-    }
-
-    /**
-     * Returns {@code true} if this {@linkplain ReferenceClassDescImpl} is
-     * equal to another {@linkplain ReferenceClassDescImpl}.  Equality is
-     * determined by the two class descriptors having equal class descriptor
-     * strings.
-     *
-     * @param o the {@code ClassDesc} to compare to this
-     *       {@code ClassDesc}
-     * @return {@code true} if the specified {@code ClassDesc}
-     *      is equal to this {@code ClassDesc}.
-     */
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o instanceof ReferenceClassDescImpl constant) {
-            return descriptor.equals(constant.descriptor);
-        }
-        return false;
-    }
-
-    @Override
-    public int hashCode() {
-        return descriptor.hashCode();
-    }
-
-    @Override
-    public String toString() {
-        return String.format("ClassDesc[%s]", displayName());
-    }
-}
diff --git a/test/jdk/java/lang/constant/ClassDescTest.java b/test/jdk/java/lang/constant/ClassDescTest.java
index 4d98e1283f0..839de27b178 100644
--- a/test/jdk/java/lang/constant/ClassDescTest.java
+++ b/test/jdk/java/lang/constant/ClassDescTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -40,9 +40,9 @@ import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
 import static org.testng.Assert.fail;
 
-/**
+/*
  * @test
- * @bug 8215510 8283075
+ * @bug 8215510 8283075 8338544
  * @compile ClassDescTest.java
  * @run testng ClassDescTest
  * @summary unit tests for java.lang.constant.ClassDesc
@@ -68,7 +68,30 @@ public class ClassDescTest extends SymbolicDescTest {
             assertEquals(r, r.componentType().arrayType());
             assertEquals(r, r.resolveConstantDesc(LOOKUP).getComponentType().describeConstable().orElseThrow().arrayType());
             assertEquals(r, Array.newInstance(r.componentType().resolveConstantDesc(LOOKUP), 0).getClass().describeConstable().orElseThrow());
+        } else {
+            assertNull(r.componentType());
         }
+
+        if (!r.isClassOrInterface()) {
+            assertEquals(r.packageName(), "");
+        }
+    }
+
+    private static String classDisplayName(Class<?> c) {
+        int arrayLevel = 0;
+        while (c.isArray()) {
+            arrayLevel++;
+            c = c.componentType();
+        }
+        String name = c.getName();
+        String simpleClassName;
+        if (c.isPrimitive()) {
+            simpleClassName = name;
+        } else {
+            int lastDot = name.lastIndexOf('.');
+            simpleClassName = lastDot == -1 ? name : name.substring(lastDot + 1);
+        }
+        return simpleClassName + "[]".repeat(arrayLevel);
     }
 
     private void testClassDesc(ClassDesc r, Class<?> c) throws ReflectiveOperationException {
@@ -77,6 +100,13 @@ public class ClassDescTest extends SymbolicDescTest {
         assertEquals(r.resolveConstantDesc(LOOKUP), c);
         assertEquals(c.describeConstable().orElseThrow(), r);
         assertEquals(ClassDesc.ofDescriptor(c.descriptorString()), r);
+        if (r.isArray()) {
+            testClassDesc(r.componentType(), c.componentType());
+        }
+        if (r.isClassOrInterface()) {
+            assertEquals(r.packageName(), c.getPackageName());
+        }
+        assertEquals(r.displayName(), classDisplayName(c));
     }
 
     public void testSymbolicDescsConstants() throws ReflectiveOperationException {
@@ -143,7 +173,8 @@ public class ClassDescTest extends SymbolicDescTest {
             assertFalse(r.isPrimitive());
             assertEquals("Ljava/lang/String;", r.descriptorString());
             assertEquals("String", r.displayName());
-            assertEquals(r.arrayType().resolveConstantDesc(LOOKUP), String[].class);
+            testClassDesc(r.arrayType(), String[].class);
+            testClassDesc(r.arrayType(3), String[][][].class);
             stringClassDescs.forEach(rr -> assertEquals(r, rr));
         }