Reflection is a feature of the Java programming language that enables a running Java program to examine and modify attributes of its classes, interfaces, fields, and methods.
GraalVM Native Image provides partial support for reflection. It uses static analysis to detect the elements of your application that are accessed using the Java Reflection API. However, because the analysis is static, it cannot always completely predict all usages of the API when the program runs. Undetected usages must be provided to the native-image
tool in the form of metadata (precomputed in code or as JSON configuration files).
The following application demonstrates the use of Java reflection and how to provide metadata for native-image
using a JSON configuration file.
-
Download and install the latest GraalVM JDK using SDKMAN!.
sdk install java 21.0.1-graal
-
Download or clone the repository and navigate into the
native-image-reflection-example
directory:git clone https://github.com/graalvm/graalvm-demos
cd graalvm-demos/native-image-reflection-example
-
Compile the application and invoke the
StringReverser()
andStringCapitalizer()
methods:$JAVA_HOME/bin/javac ReflectionExample.java
$JAVA_HOME/bin/java ReflectionExample StringReverser reverse "hello"
$JAVA_HOME/bin/java ReflectionExample StringCapitalizer capitalize "hello"
The output of each command should be
"olleh"
and"HELLO"
, respectively. (An exception is thrown if you provide any other string to identify the class or method.) -
Use the
native-image
utility to create a native executable as follows:$JAVA_HOME/bin/native-image --no-fallback ReflectionExample
NOTE: The
--no-fallback
option tonative-image
causes the utility to fail if it can not create an executable file. -
Run the resulting native executable, using the following command:
./reflectionexample StringReverser reverse "hello"
You will see a
ClassNotFoundException
exception, similar to:Exception in thread "main" java.lang.ClassNotFoundException: StringReverser at org.graalvm.nativeimage.builder/com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:122) at org.graalvm.nativeimage.builder/com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:86) at java.base@21/java.lang.Class.forName(DynamicHub.java:1346) at java.base@21/java.lang.Class.forName(DynamicHub.java:1309) at java.base@21/java.lang.Class.forName(DynamicHub.java:1302) at ReflectionExample.main(ReflectionExample.java:68) at java.base@21/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH)
This shows that, from its static analysis, the
native-image
tool was unable to determine that classStringReverser
is used by the application and therefore did not include it in the native executable.
To build a native executable containing references to the classes and methods that are accessed via reflection, provide the native-image
utility with a configuration file that specifies the classes and corresponding methods. For more information about configuration files, see Reflection Use in Native Images. You can create this file by hand, but a more convenient approach is to generate it using the tracing agent. The agent writes the configuration for you automatically when you run your application (for more information, see Assisted Configuration with Tracing Agent).
The following steps demonstrate how to use the tracing agent tool, and its output, to create a native executable that relies on reflection.
-
Create a directory
META-INF/native-image
in the working directory:mkdir -p META-INF/native-image
-
Run the application with the tracing agent enabled, as follows:
$JAVA_HOME/bin/java -agentlib:native-image-agent=config-output-dir=META-INF/native-image ReflectionExample StringReverser reverse "hello"
This command creates a file named reflection-config.json containing the name of the class
StringReverser
and itsreverse()
method.[ { "name":"StringReverser", "methods":[{"name":"reverse","parameterTypes":["java.lang.String"] }] } ]
-
Build a native executable:
$JAVA_HOME/bin/native-image ReflectionExample
The
native-image
tool automatically uses configuration files in the META-INF/native-image directory. It is recommended that the META-INF/native-image directory is on the class path, either via a JAR file or using the-cp
flag. (This avoids confusion for IDE users where a directory structure is defined by the IDE itself.) -
Test your executable:
./reflectionexample StringReverser reverse "hello"
The output of command should be
"olleh"
./reflectionexample StringCapitalizer capitalize "hello"
You will see a
ClassNotFoundException
exception again.Neither the tracing agent nor the
native-image
tool can ensure that the configuration file is complete. The agent observes and records which program elements are accessed using reflection when you run the program. In this case, thenative-image
tool has not been configured to include references to classStringCapitalizer
. -
Update the configuration to include class
StringCapitalizer
. You can manually edit the reflection-config.json file or re-run the tracing agent to update the existing configuration file using theconfig-merge-dir
option, as follows:$JAVA_HOME/bin/java -agentlib:native-image-agent=config-merge-dir=META-INF/native-image ReflectionExample StringCapitalizer capitalize "hello"
This command updates the reflection-config.json file to include the name of the class
StringCapitalizer
and itscapitalize()
method.[ { "name":"StringCapitalizer", "methods":[{"name":"capitalize","parameterTypes":["java.lang.String"] }] }, { "name":"StringReverser", "methods":[{"name":"reverse","parameterTypes":["java.lang.String"] }] } ]
-
Rebuild a native executable and run the resulting executable:
$JAVA_HOME/bin/native-image ReflectionExample
./reflectionexample StringCapitalizer capitalize "hello"
The application should now work as intended.