|
| 1 | +# Using Native Image `Preserve` Option |
| 2 | + |
| 3 | +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. |
| 4 | +GraalVM Native Image automatically supports some uses of reflection. |
| 5 | +Native Image uses static analysis to identify what classes, methods, and fields are needed by an application, but it may not detect some elements of your application that are accessed using the [Java Reflection API](https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/lang/reflect/package-summary.html). |
| 6 | +You must declare any undetected reflection usage to the `native-image` tool, either as metadata ([precomputed in code or as JSON configuration files](https://www.graalvm.org/latest/reference-manual/native-image/metadata/)) or using the `-H:Preserve` option (experimental in GraalVM for JDK 25). |
| 7 | + |
| 8 | +This guide demonstrates how to declare reflection configuration using the `-H:Preserve` option. |
| 9 | + |
| 10 | +## Preparation |
| 11 | + |
| 12 | +1. Download and install the latest GraalVM for JDK 25 (or the early access build before 2025-09-16) using [SDKMAN!](https://sdkman.io/). |
| 13 | + |
| 14 | + ```shell |
| 15 | + sdk install java 25.ea.29-graal |
| 16 | + ``` |
| 17 | + |
| 18 | +2. Download or clone this repository, and navigate into the `native-image/preserve-package` directory: |
| 19 | + |
| 20 | + ```shell |
| 21 | + git clone https://github.com/graalvm/graalvm-demos |
| 22 | + cd graalvm-demos/native-image/preserve-package |
| 23 | + ``` |
| 24 | + |
| 25 | +## Example Using Reflection on the JVM |
| 26 | + |
| 27 | +The `ReflectionExample` class uses command line argument values to reflectively create an instance of a class and invoke a method with a provided argument. The core code is: |
| 28 | + |
| 29 | +```java |
| 30 | + Class<?> clazz = Class.forName(className); |
| 31 | + Method method = clazz.getDeclaredMethod(methodName, String.class); |
| 32 | + Object result = method.invoke(null, input); |
| 33 | +``` |
| 34 | + |
| 35 | +This approach works on the JVM as long as the required classes and methods are available on the classpath. |
| 36 | + |
| 37 | +1. Compile the application and create a JAR file using Maven: |
| 38 | + |
| 39 | + ```shell |
| 40 | + ./mvnw package |
| 41 | + ``` |
| 42 | + |
| 43 | +2. Run `ReflectionExample` (the JAR entry point) to invoke the `StringReverser` action: |
| 44 | + |
| 45 | + ```shell |
| 46 | + $JAVA_HOME/bin/java -jar target/preserve-package-1.0-SNAPSHOT.jar \ |
| 47 | + org.graalvm.example.action.StringReverser reverse "hello" |
| 48 | + ``` |
| 49 | + |
| 50 | + Expected output: |
| 51 | + |
| 52 | + ```shell |
| 53 | + olleh |
| 54 | + ``` |
| 55 | + |
| 56 | +3. Run the same command for the `StringCapitalizer` action: |
| 57 | + |
| 58 | + ```shell |
| 59 | + $JAVA_HOME/bin/java -jar target/preserve-package-1.0-SNAPSHOT.jar \ |
| 60 | + org.graalvm.example.action.StringCapitalizer capitalize "hello" |
| 61 | + ``` |
| 62 | + |
| 63 | + Expected output: |
| 64 | + |
| 65 | + ```shell |
| 66 | + HELLO |
| 67 | + ``` |
| 68 | + |
| 69 | +## GraalVM Native Image |
| 70 | + |
| 71 | +You can compile the project with Native Image, specifying `ReflectionExample` as the main entry point. |
| 72 | +The [_pom.xml_](pom.xml) file uses the [Native Build Tools Maven plugin](https://graalvm.github.io/native-build-tools/latest/maven-plugin.html) to compile the project with the `native-image` tool. |
| 73 | + |
| 74 | +1. Build a native executable using the `native-default` profile (see the [_pom.xml_](pom.xml) file): |
| 75 | + |
| 76 | + ```shell |
| 77 | + ./mvnw package -Pnative-default |
| 78 | + ``` |
| 79 | + |
| 80 | +2. Run the resulting `example-default` native executable: |
| 81 | + |
| 82 | + ```bash |
| 83 | + ./target/example-default \ |
| 84 | + org.graalvm.example.action.StringReverser reverse "hello" |
| 85 | + ``` |
| 86 | + |
| 87 | + You will see a `ClassNotFoundException` similar to: |
| 88 | + |
| 89 | + ```shell |
| 90 | + Exception in thread "main" java.lang.ClassNotFoundException: org.graalvm.example.action.StringReverser |
| 91 | + at org.graalvm.nativeimage.builder/com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:339) |
| 92 | + at org.graalvm.nativeimage.builder/com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:298) |
| 93 | + at java.base@25/java.lang.Class.forName(DynamicHub.java:1758) |
| 94 | + at java.base@25/java.lang.Class.forName(DynamicHub.java:1704) |
| 95 | + at java.base@25/java.lang.Class.forName(DynamicHub.java:1691) |
| 96 | + at org.graalvm.example.ReflectionExample.main(ReflectionExample.java:56) |
| 97 | + at java.base@25/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH) |
| 98 | + ``` |
| 99 | + |
| 100 | +This error occurs because the `native-image` tool's static analysis did not determine that your application uses the `StringReverser` class, and did not include it in the native executable. |
| 101 | +
|
| 102 | +## Native Image Using `-H:Preserve` |
| 103 | +
|
| 104 | +GraalVM for JDK 25 introduces the `-H:Preserve` option. |
| 105 | +
|
| 106 | +This option lets you instruct the `native-image` tool to keep entire packages, modules, or all classes on the classpath. |
| 107 | +
|
| 108 | +<!-- This can result in larger application size. --> |
| 109 | +
|
| 110 | +In this example, both classes used via reflection are in the `org.graalvm.example.action` package. |
| 111 | +You can use `-H:Preserve=package` to keep all of the classes in that package in the native executable, even if static analysis cannot discover them. |
| 112 | +
|
| 113 | +Native Image command line arguments can be specified as `<buildArgs>` in the `native-maven-plugin` configuration. |
| 114 | +Since the `-H:Preserve` option is experimental, you must also enable its use with `-H:+UnlockExperimentalVMOptions`. |
| 115 | +For the complete plugin configuration, see the [_pom.xml_](pom.xml) file: |
| 116 | +
|
| 117 | +```xml |
| 118 | + <configuration> |
| 119 | + ... |
| 120 | + <buildArgs> |
| 121 | + <buildArg>-H:+UnlockExperimentalVMOptions</buildArg> |
| 122 | + <buildArg>-H:Preserve=package=org.graalvm.example.action</buildArg> |
| 123 | + </buildArgs> |
| 124 | + </configuration> |
| 125 | +``` |
| 126 | +
|
| 127 | +1. Build a native executable using the `native-preserve` profile, which adds `-H:Preserve=package=org.graalvm.example.action` when running the `native-image` tool: |
| 128 | +
|
| 129 | + ```shell |
| 130 | + ./mvnw package -Pnative-preserve |
| 131 | + ``` |
| 132 | +
|
| 133 | +2. Run the new `example-preserve` executable to confirm the previously missing `StringReverser` class and its methods are now included: |
| 134 | +
|
| 135 | + ```shell |
| 136 | + ./target/example-preserve \ |
| 137 | + org.graalvm.example.action.StringReverser reverse "hello" |
| 138 | + ``` |
| 139 | +
|
| 140 | + Expected output: |
| 141 | +
|
| 142 | + ```shell |
| 143 | + olleh |
| 144 | + ``` |
| 145 | +
|
| 146 | +3. Run the executable to confirm the `StringCapitalizer` class works too: |
| 147 | +
|
| 148 | + ```shell |
| 149 | + ./target/example-preserve \ |
| 150 | + org.graalvm.example.action.StringCapitalizer capitalize "hello" |
| 151 | + ``` |
| 152 | +
|
| 153 | + Expected output: |
| 154 | +
|
| 155 | + ```shell |
| 156 | + HELLO |
| 157 | + ``` |
| 158 | +
|
| 159 | +As demonstrated, `-H:Preserve` provides an easy way to ensure that Native Image includes classes not discovered by static analysis. |
| 160 | +
|
| 161 | +### Related Documentation |
| 162 | +
|
| 163 | +* [Reachability Metadata: Reflection](https://www.graalvm.org/latest/reference-manual/native-image/metadata/) |
| 164 | +* [Assisted Configuration with Tracing Agent](https://www.graalvm.org/latest/reference-manual/native-image/metadata/AutomaticMetadataCollection/#tracing-agent) |
| 165 | +* [Java Reflection API](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/reflect/package-summary.html) |
0 commit comments