diff --git a/pom.xml b/pom.xml
index d0aa9a5c..4844f5e4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -34,6 +34,12 @@
jol-core
0.17
+
+
+ org.ow2.asm
+ asm
+ 9.9
+
diff --git a/src/main/java/org/javademos/init/Java15.java b/src/main/java/org/javademos/init/Java15.java
index d402cc4c..6869a656 100644
--- a/src/main/java/org/javademos/init/Java15.java
+++ b/src/main/java/org/javademos/init/Java15.java
@@ -4,6 +4,7 @@
import org.javademos.commons.IDemo;
import org.javademos.java15.jep360.SealedClassesDemo;
+import org.javademos.java15.jep371.HiddenClassesDemo;
import org.javademos.java15.jep372.NashornRemovalDemo;
import org.javademos.java15.jep375.InstanceofPatternMatchingSecondPreview;
import org.javademos.java15.jep381.SolarisSparcRemovalDemo;
@@ -21,6 +22,8 @@ public static ArrayList getDemos() {
// JEP 360
java15DemoPool.add(new SealedClassesDemo());
+ // JEP 371
+ java15DemoPool.add(new HiddenClassesDemo());
// JEP 372
java15DemoPool.add(new NashornRemovalDemo());
// JEP 375
diff --git a/src/main/java/org/javademos/java15/jep371/HiddenClassesDemo.java b/src/main/java/org/javademos/java15/jep371/HiddenClassesDemo.java
new file mode 100644
index 00000000..45d00a31
--- /dev/null
+++ b/src/main/java/org/javademos/java15/jep371/HiddenClassesDemo.java
@@ -0,0 +1,177 @@
+package org.javademos.java15.jep371;
+
+import java.lang.invoke.MethodHandles;
+
+import org.javademos.commons.IDemo;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/// Demo for JDK 15 feature JEP 371 - Hidden Classes.
+///
+/// JEP history:
+/// - JDK 15: [JEP 371 - Hidden Classes](https://openjdk.org/jeps/371)
+///
+/// Hidden classes are classes that cannot be used directly by the bytecode of other classes.
+/// They are intended for use by frameworks that generate classes at runtime and use them indirectly, via reflection.
+/// A hidden class may be defined as a member of an access control nest, and may be unloaded independently of other classes.
+///
+/// Key features:
+/// - Not discoverable: cannot be linked by the JVM or discovered by Class.forName()
+/// - Unloadable: can be unloaded even if their defining class loader is still reachable
+/// - Access control: can be nestmates with other classes
+/// - Use case: language implementations (lambdas, proxies, etc.)
+///
+/// Further reading:
+/// - [JEP 371 Specification](https://openjdk.org/jeps/371)
+/// - [Hidden Classes Tutorial](https://www.baeldung.com/java-hidden-classes)
+///
+/// @see java.lang.invoke.MethodHandles.Lookup#defineHiddenClass
+/// @author alois.seckar@gmail.com
+public class HiddenClassesDemo implements IDemo {
+
+ @Override
+ public void demo() {
+ info(371);
+
+ try {
+ // Generate bytecode for a simple class with a greet() method
+ byte[] classBytes = generateSimpleClass();
+
+ // Get a Lookup object for defining hidden classes
+ MethodHandles.Lookup lookup = MethodHandles.lookup();
+
+ // Define a hidden class from the bytecode
+ MethodHandles.Lookup hiddenClassLookup = lookup.defineHiddenClass(
+ classBytes,
+ true, // initialize the class
+ MethodHandles.Lookup.ClassOption.NESTMATE
+ );
+
+ // Get the Class object for the hidden class
+ Class> hiddenClass = hiddenClassLookup.lookupClass();
+
+ System.out.println("✓ Hidden class created successfully!");
+ System.out.println(" Class name: " + hiddenClass.getName());
+ System.out.println(" Is hidden: " + hiddenClass.isHidden());
+ System.out.println(" Simple name: " + hiddenClass.getSimpleName());
+
+ // Demonstrate that hidden classes cannot be discovered by Class.forName()
+ System.out.println("\n✓ Attempting to discover hidden class by name...");
+ boolean discovered = canBeDiscovered(hiddenClass.getName());
+ System.out.println(" Can be discovered by Class.forName(): " + discovered);
+ System.out.println(" (Expected: false - this is the key feature!)");
+
+ // Create an instance and invoke the greet() method
+ System.out.println("\n✓ Creating instance and invoking method...");
+ Object instance = hiddenClass.getDeclaredConstructor().newInstance();
+ var method = hiddenClass.getDeclaredMethod("greet");
+ method.invoke(instance);
+
+ // Demonstrate nest membership
+ System.out.println("\n✓ Demonstrating nest membership:");
+ System.out.println(" Hidden class nest host: " + hiddenClass.getNestHost().getName());
+ System.out.println(" This class nest host: " + HiddenClassesDemo.class.getNestHost().getName());
+ System.out.println(" Are nest mates: " + hiddenClass.getNestHost().equals(HiddenClassesDemo.class.getNestHost()));
+
+ System.out.println("\n✓ Hidden class demonstration completed successfully!");
+
+ } catch (Throwable e) {
+ System.err.println("✗ Error demonstrating hidden classes: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+ /// Generates bytecode for a simple class using ASM library.
+ /// This creates a class equivalent to:
+ /// ```java
+ /// public class HiddenExample {
+ /// public HiddenExample() {}
+ /// public void greet() {
+ /// System.out.println(" → Hello from Hidden Class!");
+ /// }
+ /// }
+ /// ```
+ ///
+ /// @return byte array containing the class file bytecode
+ private byte[] generateSimpleClass() {
+ ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
+
+ // Define the class: public class HiddenExample extends Object
+ cw.visit(
+ Opcodes.V15, // Java 15
+ Opcodes.ACC_PUBLIC, // public class
+ "HiddenExample", // class name
+ null, // signature (null = not generic)
+ "java/lang/Object", // superclass
+ null // interfaces (none)
+ );
+
+ // Generate default constructor: public HiddenExample()
+ MethodVisitor constructor = cw.visitMethod(
+ Opcodes.ACC_PUBLIC, // public
+ "", // constructor name
+ "()V", // descriptor: no args, void return
+ null, // signature
+ null // exceptions
+ );
+ constructor.visitCode();
+ constructor.visitVarInsn(Opcodes.ALOAD, 0); // load 'this'
+ constructor.visitMethodInsn(
+ Opcodes.INVOKESPECIAL,
+ "java/lang/Object",
+ "",
+ "()V",
+ false
+ );
+ constructor.visitInsn(Opcodes.RETURN);
+ constructor.visitMaxs(1, 1);
+ constructor.visitEnd();
+
+ // Generate greet() method: public void greet()
+ MethodVisitor greetMethod = cw.visitMethod(
+ Opcodes.ACC_PUBLIC, // public
+ "greet", // method name
+ "()V", // descriptor: no args, void return
+ null, // signature
+ null // exceptions
+ );
+ greetMethod.visitCode();
+
+ // System.out.println(" → Hello from Hidden Class!");
+ greetMethod.visitFieldInsn(
+ Opcodes.GETSTATIC,
+ "java/lang/System",
+ "out",
+ "Ljava/io/PrintStream;"
+ );
+ greetMethod.visitLdcInsn(" → Hello from Hidden Class!");
+ greetMethod.visitMethodInsn(
+ Opcodes.INVOKEVIRTUAL,
+ "java/io/PrintStream",
+ "println",
+ "(Ljava/lang/String;)V",
+ false
+ );
+ greetMethod.visitInsn(Opcodes.RETURN);
+ greetMethod.visitMaxs(2, 1);
+ greetMethod.visitEnd();
+
+ cw.visitEnd();
+ return cw.toByteArray();
+ }
+
+ /// Attempts to discover a class by name using Class.forName().
+ /// Hidden classes should NOT be discoverable this way.
+ ///
+ /// @param className the name of the class to look up
+ /// @return true if the class can be found, false otherwise
+ private boolean canBeDiscovered(String className) {
+ try {
+ Class.forName(className);
+ return true;
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ }
+}
diff --git a/src/main/resources/JDK15Info.json b/src/main/resources/JDK15Info.json
index 884a07f4..6a4adf71 100644
--- a/src/main/resources/JDK15Info.json
+++ b/src/main/resources/JDK15Info.json
@@ -1,4 +1,11 @@
[ {
+ "jep": 371,
+ "info": {
+ "name": "JEP 371 - Hidden Classes",
+ "dscr": "Introduces hidden classes that cannot be used directly by bytecode of other classes and can be unloaded independently"
+ }
+ },
+ {
"jep": 372,
"info": {
"name": "JEP 372 - Remove the Nashorn JavaScript Engine",