Skip to main content

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.

Reflection API with constant names
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 with reflections
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.

Reflection API with non-constant names
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.

Reflection config
[
{
"name": "java.lang.String",
"methods": [
{
"name": "toUpperCase",
"parameterTypes": []
}
]
}
]
info

Refer to the JSON schema of reflection config.

With this reflection config file, the native image can be executed successfully.

Build native image with reflection config
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.

Reflection API at image build time
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.

Create native image with build time reflection
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