How to use Hidden Classes in Java

Hidden classes ( available since Java 15 ) allow developers to define classes that cannot be directly accessed by other classes in the same program. These classes are designed for use by frameworks that generate classes at runtime without using the standard classloading mechanism.

Purpose of Java Hidden Classes

Hidden classes can enable frameworks to dynamically extend existing classes without modifying their source code. This provides a powerful mechanism for adding new functionality or behavior to existing components.

You can also use Hidden classes to create interceptors that intercept and modify method calls at runtime. You could do that for security purposes or to implement custom behavior based on specific conditions.

Creating an Hidden Class

Firstly, you need to code your Hidden Class and “mask” it in Base 64 Format. We will hide the following Hello Java Class

public class Hello {

	public static String greet() {
	   return "hello";
	}

}
 

Compile the source code:

javac Hello.java

Then, we need to read the Class bytecode and encode it in Base64:

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Base64;

public class ClassToBase64Converter {

    public static void main(String[] args) {
        // Provide the path to your .class file
        String classFilePath = "path/to/YourClass.class";

        try {
            // Read the .class file as bytes
            byte[] classBytes = Files.readAllBytes(Paths.get(classFilePath));

            // Encode the bytes to Base64
            String base64Encoded = Base64.getEncoder().encodeToString(classBytes);

            // Print or use the Base64-encoded string
            System.out.println("Base64 Encoded Class:\n" + base64Encoded);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

By running the above Class, you will be able to copy from the Console the Base64 of your Class’ Bytecode:

java hidden classes example

Finally, we will invoke the Hello Class from within another Class using the Base64 of the ByteCode:

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Base64;

public class HiddenClassExample {

    static final String CLASS_IN_BASE64 = "yv66vgAAAD0AEQoAAgADBwAEDAAFAAYBABBqYXZhL2xhbmcvT2JqZWN0AQAGPGluaXQ+AQADKClWCAAIAQAFaGVsbG8HAAoBAAxqYXZhMTUvSGVsbG8BAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAFZ3JlZXQBABQoKUxqYXZhL2xhbmcvU3RyaW5nOwEAClNvdXJjZUZpbGUBAApIZWxsby5qYXZhACEACQACAAAAAAACAAEABQAGAAEACwAAAB0AAQABAAAABSq3AAGxAAAAAQAMAAAABgABAAAAAwAJAA0ADgABAAsAAAAbAAEAAAAAAAMSB7AAAAABAAwAAAAGAAEAAAAGAAEADwAAAAIAEA==";

    public static void main(String[] args) throws Throwable {
        testHiddenClass();
    }

    // create a hidden class and run its static method
    public static void testHiddenClass() throws Throwable {

        byte[] classInBytes = Base64.getDecoder().decode(CLASS_IN_BASE64);

        Class<?> proxy = MethodHandles.lookup()
                .defineHiddenClass(classInBytes,
                        true, MethodHandles.Lookup.ClassOption.NESTMATE)
                .lookupClass();


        System.out.println(proxy.getName());

        MethodHandle mh = MethodHandles.lookup().findStatic(proxy,
                "greet",
                MethodType.methodType(String.class));

        String status = (String) mh.invokeExact();
        System.out.println(status);

    }

}

The code creates a hidden class using the defineHiddenClass() method of the MethodHandles.Lookup class. This method takes the bytecode, a flag indicating whether the class is synthetic, and an option specifying the class loader to use as arguments. In this case, the option is set to NESTMATE, which means that the hidden class will be loaded in the same class loader as the current class.

Then, the code calls the lookupClass() method of the Lookup object returned by the defineHiddenClass() method to obtain the hidden class itself. This object represents the hidden class and can be used to access its methods and fields.

java hidden class example

As you can see, we have called the greet method successfully of the Hidden Class.

Conclusion

Hidden classes offer a valuable tool for frameworks and developers. Their ability to hide classes and limit their lifecycle makes them well-suited for various scenarios, including adaptive serialization, dynamic extensions, and secure interceptors.

Found the article helpful? if so please follow us on Socials