Java 15 Hidden Classes Explained with Examples

Introduction Many frameworks generate classes while an application is running: lambda expressions are turned into classes at runtime, dynamic proxies implement interfaces on the fly, and languages such as Groovy, JRuby and Kotlin scripts compile code into classes as they run. These generated classes are implementation details - no other code should find them by name - and they are often needed only briefly. Before Java 15, the JDK had no standard way to define such classes, so frameworks used the internal, unsupported sun.misc.Unsafe.defineAnonymousClass(). Java 15Click to read tutorial: Overview of New Features in Java 15 (JEP 371) added hidden classes as a supported replacement. This tutorial explains hidden classes: what makes them hidden, why frameworks need them, and how to define one with MethodHandles.Lookup.defineHiddenClass(), with a runnable example. What makes a class hidden A hidden class is created from an array of class-file bytes, like any dynamically generated class, but it has three special properties -
  • Not discoverable: it is not registered with any class loader under its name, so Class.forName() cannot find it, and no other class can refer to it in its bytecode - not even by a static type in a method signature. It can only be used through the Class object returned when it is defined, typically via reflection, method handles or an interface it implements.
  • Unloadable on its own: a hidden class can be unloaded as soon as it is no longer reachable, even if the class loader which defined it is still in use. Normal classes are only unloaded together with their whole class loader. This avoids memory leaks in frameworks which generate many short-lived classes.
  • Access as a nestmate: it can optionally be defined as a nestmateClick to read: nestmate of the class that created it, giving it access to that class's private members.
Its name, as returned by getName(), has a slash and a unique suffix, such as Greeter/0x0000000801001000 - not a valid binary class name, which guarantees it cannot clash with or be looked up as a normal class.
Defining a hidden class A hidden class is defined with a MethodHandles.Lookup object, whose class becomes the lookup class - the hidden class is created in the same package and with the same access rights. In a real framework, the class bytes would be generated with a bytecode library or the Class-File APIClick to read: Class-File API. To keep the example short, it reads the bytes of an ordinary compiled class, HelloGreeter, and defines a hidden class from them.
An interface the hidden class implements
//Greeter.java
public interface Greeter {
  String greet(String name);
}
The class whose bytes are used
//HelloGreeter.java
public class HelloGreeter implements Greeter {
  public String greet(String name) {
    return "Hello, " + name + " from " + getClass().getName();
  }
}
Defining and using a hidden class
//HiddenClassDemo.java
import java.io.InputStream;
import java.lang.invoke.MethodHandles;

public class HiddenClassDemo {
  public static void main(String[] args) throws Exception {
    byte[] bytes;
    try (InputStream in = HiddenClassDemo.class.getResourceAsStream("HelloGreeter.class")) {
      bytes = in.readAllBytes();               // stands in for bytes generated at runtime
    }

    MethodHandles.Lookup lookup = MethodHandles.lookup();
    Class<?> hidden = lookup.defineHiddenClass(bytes, true).lookupClass();

    System.out.println("isHidden(): " + hidden.isHidden());
    System.out.println("Name starts with: " + hidden.getName().substring(0, hidden.getName().indexOf('/') + 1) + "0x...");

    Greeter greeter = (Greeter) hidden.getDeclaredConstructor().newInstance();
    System.out.println(greeter.greet("Tom").replaceAll("/0x[0-9a-f]+", "/0x..."));

    try {
      Class.forName(hidden.getName());
    } catch (ClassNotFoundException e) {
      System.out.println("Class.forName() cannot find it: ClassNotFoundException");
    }
    System.out.println("The normal HelloGreeter is a different class: " + (hidden != HelloGreeter.class));
  }
}
 OUTPUT of the above code
isHidden(): true
Name starts with: HelloGreeter/0x...
Hello, Tom from HelloGreeter/0x...
Class.forName() cannot find it: ClassNotFoundException
The normal HelloGreeter is a different class: true
Explanation of the code
  • lookup.defineHiddenClass(bytes, true) defines the hidden class and returns a new Lookup whose lookup class is the hidden class. The true argument initializes the class immediately. Optional ClassOption arguments, such as NESTMATE and STRONG, control nestmate access and whether the class can be unloaded independently of its class loader.
  • isHidden(), a new method on Class, returns true, and the name has a slash and a unique suffix (replaced by "0x..." in the output, since it changes on every run).
  • The hidden class is used through the Greeter interface it implements - the usual way frameworks expose generated code - and its getClass().getName() shows the hidden name.
  • Class.forName() cannot find it, even with its exact name, and it is a different class from the normal HelloGreeter, although it was defined from the same bytes.
Who uses hidden classes Application developers rarely define hidden classes themselves; they benefit from them indirectly -
  • Lambdas: since Java 15, the JDK's own LambdaMetafactory defines the classes behind lambda expressions as hidden classes. Printing a lambda's class name shows names like Main$$Lambda/0x....
  • Frameworks: bytecode-generating libraries and frameworks, such as those creating proxies or serializers, can use hidden classes instead of internal APIs.
  • Language runtimes: JVM languages which compile scripts at runtime can generate many small classes and have them unloaded when no longer needed.
In the same release, sun.misc.Unsafe.defineAnonymousClass() was deprecated for removal; it was removed in Java 17. Moving frameworks off this internal API was also part of the strong encapsulation of JDK internalsClick to read tutorial: Strong Encapsulation of JDK Internals in Java 16 and 17. Summary In this tutorial we saw that hidden classes, added in Java 15, are classes which frameworks generate at runtime and which cannot be discovered by name, can be unloaded independently, and can optionally access their creator's private members. We defined a hidden class from class-file bytes with Lookup.defineHiddenClass(), used it through an interface, and saw that Class.forName() cannot find it. Lastly, we saw that lambdas and frameworks are the main users of hidden classes, replacing the internal Unsafe.defineAnonymousClass(). Next, read the overview of new features in Java 16Click to read tutorial: Overview of New Features in Java 16.