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