diff a/src/java.base/share/classes/java/lang/constant/ClassDescImpl.java b/src/java.base/share/classes/java/lang/constant/ClassDescImpl.java
--- /dev/null
+++ b/src/java.base/share/classes/java/lang/constant/ClassDescImpl.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2018, 2023, 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 java.lang.constant;
+
+import java.lang.invoke.MethodHandles;
+
+import static java.lang.constant.ConstantUtils.*;
+import static java.util.Objects.requireNonNull;
+
+/**
+ * A <a href="package-summary.html#nominal">nominal descriptor</a> for a class,
+ * interface, or array type.  A {@linkplain ClassDescImpl} corresponds to a
+ * {@code Constant_Class_info} entry in the constant pool of a classfile.
+ */
+final class ClassDescImpl implements ClassDesc {
+    private final String 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
+     */
+    ClassDescImpl(String descriptor) {
+        requireNonNull(descriptor);
+        int len = ConstantUtils.skipOverFieldSignature(descriptor, 0, descriptor.length(), false);
+        if (len == 0 || len == 1
+            || len != descriptor.length())
+            throw new IllegalArgumentException(String.format("not a valid reference type descriptor: %s", descriptor));
+        this.descriptor = descriptor;
+    }
+
+    @Override
+    public String descriptorString() {
+        return descriptor;
+    }
+
+    @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(dropFirstAndLastChar(descriptor)));
+    }
+
+    /**
+     * 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 ClassDescImpl} is
+     * equal to another {@linkplain ClassDescImpl}.  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 == null || getClass() != o.getClass()) return false;
+
+        ClassDesc constant = (ClassDesc) o;
+        return descriptor.equals(constant.descriptorString());
+    }
+
+    @Override
+    public int hashCode() {
+        return descriptor.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("ClassDesc[%s]", displayName());
+    }
+}
