diff --git a/src/java.compiler/share/classes/javax/lang/model/util/Elements.java b/src/java.compiler/share/classes/javax/lang/model/util/Elements.java
index d3326ba7018f5..2e2d7477a5763 100644
--- a/src/java.compiler/share/classes/javax/lang/model/util/Elements.java
+++ b/src/java.compiler/share/classes/javax/lang/model/util/Elements.java
@@ -758,6 +758,29 @@ default boolean isAutomaticModule(ModuleElement module) {
         return false;
     }
 
+    /**
+     * {@return the class body of an {@code enum} constant if the
+     * argument is an {@code enum} constant declared with an optional
+     * class body, {@code null} otherwise}
+     *
+     * @implSpec
+     * The default implementation of this method throws {@code
+     * UnsupportedOperationException} if the argument is an {@code
+     * enum} constant and throws an {@code IllegalArgumentException}
+     * if it is not.
+     *
+     * @param enumConstant an enum constant
+     * @throws IllegalArgumentException if the argument is not an {@code enum} constant
+     * @jls 8.9.1 Enum Constants
+     * @since 22
+     */
+    default TypeElement getEnumConstantBody(VariableElement enumConstant) {
+        switch(enumConstant.getKind()) {
+        case ENUM_CONSTANT -> throw new UnsupportedOperationException();
+        default            -> throw new IllegalArgumentException("Argument not an enum constant");
+        }
+    }
+
     /**
      * Returns the record component for the given accessor. Returns
      * {@code null} if the given method is not a record component
diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/model/JavacElements.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/model/JavacElements.java
index 0c0bb4a695958..bdd25c066e52d 100644
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/model/JavacElements.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/model/JavacElements.java
@@ -71,6 +71,7 @@
 import static com.sun.tools.javac.code.Kinds.Kind.*;
 import static com.sun.tools.javac.code.Scope.LookupKind.NON_RECURSIVE;
 import static com.sun.tools.javac.code.TypeTag.CLASS;
+import com.sun.tools.javac.comp.Attr;
 import com.sun.tools.javac.comp.Modules;
 import com.sun.tools.javac.comp.Resolve;
 import com.sun.tools.javac.comp.Resolve.RecoveryLoadClass;
@@ -93,6 +94,7 @@ public class JavacElements implements Elements {
     private final Names names;
     private final Types types;
     private final Enter enter;
+    private final Attr attr;
     private final Resolve resolve;
     private final JavacTaskImpl javacTaskImpl;
     private final Log log;
@@ -114,6 +116,7 @@ protected JavacElements(Context context) {
         names = Names.instance(context);
         types = Types.instance(context);
         enter = Enter.instance(context);
+        attr = Attr.instance(context);
         resolve = Resolve.instance(context);
         JavacTask t = context.get(JavacTask.class);
         javacTaskImpl = t instanceof JavacTaskImpl taskImpl ? taskImpl : null;
@@ -725,6 +728,37 @@ public boolean isAutomaticModule(ModuleElement module) {
         return (msym.flags() & Flags.AUTOMATIC_MODULE) != 0;
     }
 
+    @Override @DefinedBy(Api.LANGUAGE_MODEL)
+    public TypeElement getEnumConstantBody(VariableElement enumConstant) {
+        if (enumConstant.getKind() == ElementKind.ENUM_CONSTANT) {
+            JCTree enumBodyTree = getTreeAlt(enumConstant);
+            JCTree enclosingEnumTree = getTreeAlt(enumConstant.getEnclosingElement());
+
+            if (enumBodyTree instanceof JCVariableDecl decl
+                && enclosingEnumTree instanceof JCClassDecl clazz
+                && decl.init instanceof JCNewClass nc
+                && nc.def != null) {
+                if ((clazz.sym.flags_field & Flags.UNATTRIBUTED) != 0) {
+                    attr.attribClass(clazz.pos(), clazz.sym);
+                }
+                return nc.def.sym; // ClassSymbol for enum constant body
+            } else {
+                return null;
+            }
+        } else {
+            throw new IllegalArgumentException("Argument not an enum constant");
+        }
+    }
+
+    private JCTree getTreeAlt(Element e) {
+        Symbol sym = cast(Symbol.class, e);
+        Env<AttrContext> enterEnv = getEnterEnv(sym);
+        if (enterEnv == null)
+            return null;
+        JCTree tree = TreeInfo.declarationFor(sym, enterEnv.tree);
+        return tree;
+    }
+
     @Override @DefinedBy(Api.LANGUAGE_MODEL)
     public boolean isCompactConstructor(ExecutableElement e) {
         return (((MethodSymbol)e).flags() & Flags.COMPACT_RECORD_CONSTRUCTOR) != 0;
diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/PrintingProcessor.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/PrintingProcessor.java
index 29d3418bbfa56..ee49a5b344d9c 100644
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/PrintingProcessor.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/PrintingProcessor.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 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
@@ -184,6 +184,16 @@ public PrintingElementVisitor visitType(TypeElement e, Boolean p) {
             NestingKind nestingKind = e.getNestingKind();
 
             if (NestingKind.ANONYMOUS == nestingKind) {
+                // Print nothing for an anonymous class used for an
+                // enum constant body.
+                TypeMirror supertype = e.getSuperclass();
+                if (supertype.getKind() != TypeKind.NONE) {
+                    TypeElement superClass = (TypeElement)(((DeclaredType)supertype).asElement());
+                    if (superClass.getKind() == ENUM) {
+                        return this;
+                    }
+                }
+
                 // Print out an anonymous class in the style of a
                 // class instance creation expression rather than a
                 // class declaration.
@@ -693,6 +703,12 @@ private void printInterfaces(TypeElement e) {
         }
 
         private void printPermittedSubclasses(TypeElement e) {
+            if (e.getKind() == ENUM) {
+                // any permitted classes on an enum are anonymous
+                // classes for enum bodies, elide.
+                return;
+            }
+
             List<? extends TypeMirror> subtypes = e.getPermittedSubclasses();
             if (!subtypes.isEmpty()) { // could remove this check with more complicated joining call
                 writer.print(" permits ");
diff --git a/test/langtools/tools/javac/processing/model/util/elements/TestGetEnumConstantBody.java b/test/langtools/tools/javac/processing/model/util/elements/TestGetEnumConstantBody.java
new file mode 100644
index 0000000000000..0cc526fc386fd
--- /dev/null
+++ b/test/langtools/tools/javac/processing/model/util/elements/TestGetEnumConstantBody.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (c) 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.
+ *
+ * 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.
+ */
+
+/*
+ * @test
+ * @bug 8312418
+ * @summary Test Elements.getEnumConstantBody
+ * @library /tools/javac/lib
+ * @build   JavacTestingAbstractProcessor TestGetEnumConstantBody
+ * @compile -processor TestGetEnumConstantBody -XDshould.stop-at=FLOW TestGetEnumConstantBody.java
+ */
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.*;
+import java.util.function.*;
+import javax.annotation.processing.*;
+import javax.lang.model.element.*;
+import javax.lang.model.util.*;
+import javax.lang.model.type.*;
+
+/**
+ * Test basic workings of Elements.getEnumConstantBody
+ */
+public class TestGetEnumConstantBody extends JavacTestingAbstractProcessor {
+    private Elements vacuousElements = new VacuousElements();
+    private Set<Element> allElements = new HashSet<>();
+    private int round;
+
+    public boolean process(Set<? extends TypeElement> annotations,
+                           RoundEnvironment roundEnv) {
+
+        allElements.addAll(roundEnv.getRootElements());
+
+        // In the innermost loop, examine the fields defined by the the nested classes
+        for (TypeElement typeRoot : ElementFilter.typesIn(allElements) ) {
+            if (typeRoot.getQualifiedName().contentEquals("Gen")) {
+                continue;
+            }
+
+            boolean elementSeen = false;
+
+            for (TypeElement typeElt : ElementFilter.typesIn(typeRoot.getEnclosedElements()) ) {
+                System.out.println("Testing type " + typeElt);
+
+                for (VariableElement field : ElementFilter.fieldsIn(typeElt.getEnclosedElements()) ) {
+                    elementSeen = true;
+                    System.out.println(field);
+                    switch (field.getKind()) {
+                    case FIELD         -> expectIAE(field);
+                    case ENUM_CONSTANT -> testEnumConstant(field, typeElt);
+                    default            -> throw new RuntimeException("Unexpected field kind seen");
+                    }
+                }
+            }
+
+            if (!elementSeen) {
+                throw new RuntimeException("No elements seen.");
+            }
+        }
+        switch (round++) {
+            case 0:
+                try (Writer w = processingEnv.getFiler().createSourceFile("Cleaned").openWriter()) {
+                    w.write("""
+                            class Enclosing {
+                                enum Cleaned {
+                                    @TestGetEnumConstantBody.ExpectedBinaryName("Enclosing$Cleaned$2")
+                                    A(new Object() {}) {
+                                        void test(Gen g) {
+                                            g.run();
+                                        }
+                                    },
+                                    B,
+                                    @TestGetEnumConstantBody.ExpectedBinaryName("Enclosing$Cleaned$4")
+                                    C(new Object() {}) {
+                                    };
+
+                                    private Cleaned() {}
+
+                                    private Cleaned(Object o) {}
+                                }
+                            }
+                            """);
+                } catch (IOException ex) {
+                    throw new RuntimeException(ex);
+                }
+                break;
+            case 1:
+                try (Writer w = processingEnv.getFiler().createSourceFile("Gen").openWriter()) {
+                    w.write("""
+                            public class Gen {
+                                public void run() {}
+                            }
+                            """);
+                } catch (IOException ex) {
+                    throw new RuntimeException(ex);
+                }
+                break;
+        }
+        return true;
+    }
+
+    private String computeExpectedBinaryName(VariableElement e) {
+        ExpectedBinaryName ebn = e.getAnnotation(ExpectedBinaryName.class);
+        return (ebn == null) ? null : ebn.value();
+    }
+
+    private void expectIAE(VariableElement variable) {
+        expectException0(() ->  elements.getEnumConstantBody(variable),
+                         "Expected exception not thrown");
+
+        expectException0(() ->  vacuousElements.getEnumConstantBody(variable),
+                         "Expected vacuous exception not thrown");
+    }
+
+    private void expectException0(Supplier<TypeElement> supplier, String message) {
+        try {
+            var typeElement = supplier.get();
+            messager.printError(message, typeElement);
+        } catch (IllegalArgumentException iae) {
+            ; // Expected
+        }
+    }
+
+    void expectUOE(VariableElement field) {
+        try {
+            var result = vacuousElements.getEnumConstantBody(field);
+            messager.printError("Unexpected non-exceptional result returned", field);
+
+        } catch(UnsupportedOperationException uoe) {
+            ; // Expected
+        }
+    }
+
+    private void testEnumConstant(VariableElement field,
+                                  TypeElement enclosingClass) {
+        String expectedBinaryName = computeExpectedBinaryName(field);
+        boolean expectEnumConstantBody = expectedBinaryName != null;
+
+        System.out.println("\tTesting enum constant " + field + " expected " + expectEnumConstantBody);
+        expectUOE(field);
+
+        TypeElement enumConstantBody = elements.getEnumConstantBody(field);
+
+        if (Objects.nonNull(enumConstantBody) != expectEnumConstantBody) {
+            messager.printError("Unexpected body value", field);
+        }
+
+        if (enumConstantBody != null) {
+            testEnumConstantBody(enumConstantBody, expectedBinaryName, enclosingClass);
+        }
+
+        System.out.println("\t constant body " + enumConstantBody);
+    }
+
+    /*
+     * From JLS 8.9.1:
+     *
+     * "The optional class body of an enum constant implicitly
+     * declares an anonymous class (15.9.5) that (i) is a direct
+     * subclass of the immediately enclosing enum class (8.1.4), and
+     * (ii) is final (8.1.1.2). The class body is governed by the
+     * usual rules of anonymous classes; in particular it cannot
+     * contain any constructors. Instance methods declared in these
+     * class bodies may be invoked outside the enclosing enum class
+     * only if they override accessible methods in the enclosing enum
+     * class (8.4.8)."
+     */
+    private void testEnumConstantBody(TypeElement enumConstBody, String expectedBinaryName, TypeElement enumClass) {
+        if (enumConstBody.getNestingKind() != NestingKind.ANONYMOUS) {
+            messager.printError("Class body not an anonymous class", enumConstBody);
+        }
+
+        // Get the TypeElement for the direct superclass.
+        TypeElement superClass =
+            (TypeElement)(((DeclaredType)enumConstBody.getSuperclass()).asElement());
+
+        if (!superClass.equals(enumClass)) {
+            messager.printError("Class body is not a direct subclass of the enum", enumConstBody);
+        }
+
+        if (!enumConstBody.getModifiers().contains(Modifier.FINAL)) {
+            messager.printError("Modifier final missing on class body", enumConstBody);
+        }
+
+        if (!elements.getBinaryName(enumConstBody).contentEquals(expectedBinaryName)) {
+            messager.printError("Unexpected binary name, expected: " + expectedBinaryName +
+                                                       ", but was: " + elements.getBinaryName(enumConstBody), enumConstBody);
+        }
+
+        return;
+    }
+
+
+    @interface ExpectedBinaryName {
+        String value();
+    }
+
+    // Nested classes hosting a variety of different kinds of fields.
+
+    private static enum Body {
+        @ExpectedBinaryName("TestGetEnumConstantBody$Body$1")
+        GOLGI(true) {
+            public boolean isOrganelle() {return true;}
+        },
+
+        @ExpectedBinaryName("TestGetEnumConstantBody$Body$2")
+        HEAVENLY(true) {
+            public boolean isCelestial() {return true;}
+        };
+
+        private Body(boolean predicate) {
+            this.predicate = predicate;
+        }
+
+        private boolean predicate;
+
+        public static int field = 42;
+
+        public void method() {return;}
+    }
+
+    private static enum MetaSyntaxVar {
+        FOO("foo"),
+        BAR("bar");
+
+        private String lower;
+        private MetaSyntaxVar(String lower) {
+            this.lower = lower;
+        }
+
+        int   BAZ  = 0;
+        float QUUX = 0.1f;
+    }
+
+    // Instance and static fields.
+    public static class FieldHolder {
+        public static final int f1 = 1;
+        public static final String s = "s";
+
+        private Object data;
+        public FieldHolder(Object data) {
+            this.data = data;
+        }
+    }
+}