Reflection
Reflection is a powerful feature that enables Java programs to examine classes, methods, fields, and their properties in the runtime. To support reflection in native image, reflectively accessed program elements need to be known in the build time.
Automatic Detection
Native Image tries to resolve program elements through a static analysis that detects calls to the Reflection API.
Calls to the following methods will be intercepted:
Class.forName(String)
Class.forName(String, ClassLoader)
Class.getDeclaredField(String)
Class.getField(String)
Class.getDeclaredMethod(String, Class[])
Class.getMethod(String, Class[])
Class.getDeclaredConstructor(Class[])
Class.getConstructor(Class[])
If arguments to these calls can be resolved to constants, Native Image tries to resolve the target elements. If the target elements can be resolved, the calls are removed and the resolved elements are embedded directly in the code. If the target elements cannot be resolved, the calls are replaced with code that throws the corresponding exception at the runtime.
Because Native Image can only do static analysis, only constants can be resolved in the image build time.
With Constant Names
The following code shows an example of using Reflection API. Arguments of Class.forName
and Class.getDeclaredMethod
are constants. This usage of Reflection API can be detected by Native Image, so it works fine in native image.
package io.vividcode.graalvm.reflection;
import java.lang.reflect.Method;
public class ReflectionExample {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("java.lang.String");
Method method = clazz.getDeclaredMethod("toUpperCase");
String result = (String) method.invoke("Hello World");
System.out.println(result);
}
}
Use the following command to create a native image.
native-image -H:Name=simple-reflection \
-H:Class=io.vividcode.graalvm.reflection.ReflectionExample \
--no-fallback
The static analysis of Native Image is smart enough to detect some valid cases. For example, Class.forName("java.lang." + "String")
also works. A method that returns a constant can also be resolved.
If we change the class name to a non-existing class com.example.Dummy
, the native image can still be built successfully. However, executing the native image throws a java.lang.ClassNotFoundException
exception.
Exception in thread "main" java.lang.ClassNotFoundException: com.example.Dummy. This exception was synthesized during native image building fr
om a call to java.lang.Class.forName(String) with constant arguments.
at io.vividcode.graalvm.reflection.ReflectionExample.main(ReflectionExample.java:8)
at java.base@21.0.1/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH)
If the method name is changed to a non-existing name, the thrown exception will be java.lang.NoSuchMethodException
.
With Non-constant Names
In the code below, the argument of Class.forName
is not a constant, but a variable className
. Although the variable className
has the correct value java.lang.String
, it's not possible for Native Image to resolve this in the image build time.
package io.vividcode.graalvm.reflection;
import java.lang.reflect.Method;
public class NonConstantReflectionExample {
public static void main(String[] args) throws Exception {
String className = "java/lang/String".replace("/", ".");
Class<?> clazz = Class.forName(className);
Method method = clazz.getDeclaredMethod("toUpperCase");
String result = (String) method.invoke("Hello World");
System.out.println(result);
}
}
The result of Native Image build depends on the fallback configuration.
If fallback image is allowed, a fallback image will be created. native-image
command prints out warnings in this case.
Warning: Reflection method java.lang.Class.getDeclaredMethod invoked at io.vividcode.graalvm.reflection.NonConstantReflectionExample.main(NonConstantReflectionExample.java:10)
Warning: Aborting stand-alone image build due to reflection use without configuration.
If no fallback image is allowed with --no-fallback
option, the image can be created. However, executing the native image throws an java.lang.NoSuchMethodException
. The exception message may look strange, because the java.lang.String.toUpperCase()
method is not found. This is because no class loading attempts are performed in the native image.
Exception in thread "main" java.lang.NoSuchMethodException: java.lang.String.toUpperCase()
at java.base@21.0.1/java.lang.Class.checkMethod(DynamicHub.java:1065)
at java.base@21.0.1/java.lang.Class.getDeclaredMethod(DynamicHub.java:1155)
at io.vividcode.graalvm.reflection.NonConstantReflectionExample.main(NonConstantReflectionExample.java:10)
at java.base@21.0.1/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH)
Manual Configuration
If reflectively accessed elements are specified as non-constants, those elements need to be declared explicitly and provided to the native-image
tool. This is done by using the -H:ReflectionConfigurationFiles
option.
Reflection config is a JSON file. It specifies that the toUpperCase
method of java.lang.String
is reflectively accessed.
[
{
"name": "java.lang.String",
"methods": [
{
"name": "toUpperCase",
"parameterTypes": []
}
]
}
]
Refer to the JSON schema of reflection config.
With this reflection config file, the native image can be executed successfully.
native-image -cp . --install-exit-handlers --no-fallback \
-H:Name=manual-reflection \
-H:ReflectionConfigurationFiles=./reflect-config.json \
-H:Class=io.vividcode.graalvm.reflection.NonConstantReflectionExample
Reflection at Image Build Time
Reflection API can be used without explicit configuration at the image build time. If Reflection API is used in the static initializer of a class, and this class is initialized at the build time, the no extra configuration is required for those reflectively accessed elements.
In the code below, the static reference to toUpperCase
method is initialized in the class's static initialization block.
package io.vividcode.graalvm.reflection;
import java.lang.reflect.Method;
public class BuildTimeReflection {
private static Method toUpperCase;
static {
try {
String className = "java/lang/String".replace("/", ".");
Class<?> clazz = Class.forName(className);
toUpperCase = clazz.getDeclaredMethod("toUpperCase");
} catch (ClassNotFoundException | NoSuchMethodException e) {
System.err.println("Cannot get method");
}
}
public static void main(String[] args) throws Exception {
String result = (String) toUpperCase.invoke("Hello World");
System.out.println(result);
}
}
When creating the native image, it's important to make sure that the BuildTimeReflection
class is initialized at image build time.
native-image -H:Name=build-time-reflection \
-H:Class=io.vividcode.graalvm.reflection.BuildTimeReflection \
--no-fallback \
--initialize-at-build-time=io.vividcode.graalvm.reflection.BuildTimeReflection