|
1 | | -# Using Native Image `Preserve` Option |
| 1 | +# GraalVM Native Image: Using the `-H:Preserve` Option |
2 | 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). |
| 3 | +This demo shows how to use GraalVM Native Image’s experimental -H:Preserve |
| 4 | +option to ensure classes accessed via reflection are included in your native |
| 5 | +executable. You’ll learn how to identify missing classes, use build reports, and |
| 6 | +apply the new option for reliable runtime behavior. |
7 | 7 |
|
8 | | -This guide demonstrates how to declare reflection configuration using the `-H:Preserve` option. |
| 8 | +## Introduction |
9 | 9 |
|
10 | | -## Preparation |
| 10 | +Java reflection lets a running program inspect and invoke classes, fields, and |
| 11 | +methods at runtime. GraalVM Native Image supports many common cases |
| 12 | +automatically by performing static analysis to discover what must be included in |
| 13 | +the native executable. However, elements that are only accessed reflectively may |
| 14 | +not be detected and therefore can be missing at runtime unless you declare them. |
11 | 15 |
|
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/). |
| 16 | +You can declare reflective access either via metadata (in code or JSON) or, |
| 17 | +starting in GraalVM 25, via the experimental `-H:Preserve` option. This demo |
| 18 | +shows how to use `-H:Preserve` to keep specific packages available at runtime. |
13 | 19 |
|
14 | | - ```shell |
15 | | - sdk install java 25.ea.29-graal |
16 | | - ``` |
| 20 | +## Prerequisites |
17 | 21 |
|
18 | | -2. Download or clone this repository, and navigate into the `native-image/preserve-package` directory: |
| 22 | +- GraalVM 25 |
| 23 | +- Maven 3.9+ |
| 24 | +- SDKMAN! (recommended for installing GraalVM) |
19 | 25 |
|
20 | | - ```shell |
21 | | - git clone https://github.com/graalvm/graalvm-demos |
22 | | - cd graalvm-demos/native-image/preserve-package |
23 | | - ``` |
| 26 | +Install GraalVM 25 with SDKMAN!: |
24 | 27 |
|
25 | | -## Example Using Reflection on the JVM |
| 28 | +```bash |
| 29 | +sdk install java 25-graal |
| 30 | +sdk use java 25-graal |
| 31 | +``` |
26 | 32 |
|
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: |
| 33 | +Clone the example repository: |
28 | 34 |
|
29 | | -```java |
30 | | - Class<?> clazz = Class.forName(className); |
31 | | - Method method = clazz.getDeclaredMethod(methodName, String.class); |
32 | | - Object result = method.invoke(null, input); |
| 35 | +```bash |
| 36 | +git clone https://github.com/graalvm/graalvm-demos |
| 37 | +cd graalvm-demos/native-image/preserve-package |
33 | 38 | ``` |
34 | 39 |
|
35 | | -This approach works on the JVM as long as the required classes and methods are available on the classpath. |
| 40 | +## How the Example Works |
36 | 41 |
|
37 | | -1. Compile the application and create a JAR file using Maven: |
| 42 | +`ReflectionExample` uses command-line arguments to reflectively: |
| 43 | +- load a class by name, |
| 44 | +- find a method, |
| 45 | +- and invoke it with a string argument. |
38 | 46 |
|
39 | | - ```shell |
40 | | - ./mvnw package |
41 | | - ``` |
| 47 | +The core code is: |
42 | 48 |
|
43 | | -2. Run `ReflectionExample` (the JAR entry point) to invoke the `StringReverser` action: |
| 49 | +```java |
| 50 | +Class<?> clazz = Class.forName(className); |
| 51 | +Method method = clazz.getDeclaredMethod(methodName, String.class); |
| 52 | +Object result = method.invoke(null, input); |
| 53 | +``` |
44 | 54 |
|
45 | | - ```shell |
46 | | - $JAVA_HOME/bin/java -jar target/preserve-package-1.0-SNAPSHOT.jar \ |
47 | | - org.graalvm.example.action.StringReverser reverse "hello" |
48 | | - ``` |
| 55 | +Two sample actions are provided: |
| 56 | +- `org.graalvm.example.action.StringReverser#reverse(String)` |
| 57 | +- `org.graalvm.example.action.StringCapitalizer#capitalize(String)` |
49 | 58 |
|
50 | | - Expected output: |
| 59 | +This works on the JVM as long as those classes are on the classpath. |
51 | 60 |
|
52 | | - ```shell |
53 | | - olleh |
54 | | - ``` |
| 61 | +### Run on the JVM |
55 | 62 |
|
56 | | -3. Run the same command for the `StringCapitalizer` action: |
| 63 | +1. Build the JAR: |
57 | 64 |
|
58 | | - ```shell |
59 | | - $JAVA_HOME/bin/java -jar target/preserve-package-1.0-SNAPSHOT.jar \ |
60 | | - org.graalvm.example.action.StringCapitalizer capitalize "hello" |
61 | | - ``` |
| 65 | +```bash |
| 66 | +./mvnw package |
| 67 | +``` |
62 | 68 |
|
63 | | - Expected output: |
| 69 | +2. Invoke `StringReverser`: |
64 | 70 |
|
65 | | - ```shell |
66 | | - HELLO |
67 | | - ``` |
| 71 | +```bash |
| 72 | +java -jar target/preserve-package-1.0-SNAPSHOT.jar \ |
| 73 | + org.graalvm.example.action.StringReverser reverse "hello" |
| 74 | +``` |
68 | 75 |
|
69 | | -## GraalVM Native Image |
| 76 | +Expected output: |
70 | 77 |
|
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. |
| 78 | +``` |
| 79 | +olleh |
| 80 | +``` |
73 | 81 |
|
74 | | -1. Build a native executable using the `native-default` profile (see the [_pom.xml_](pom.xml) file): |
| 82 | +3. Invoke `StringCapitalizer`: |
75 | 83 |
|
76 | | - ```shell |
77 | | - ./mvnw package -Pnative-default |
78 | | - ``` |
| 84 | +```bash |
| 85 | +java -jar target/preserve-package-1.0-SNAPSHOT.jar \ |
| 86 | + org.graalvm.example.action.StringCapitalizer capitalize "hello" |
| 87 | +``` |
79 | 88 |
|
80 | | -2. Run the resulting `example-default` native executable: |
| 89 | +Expected output: |
81 | 90 |
|
82 | | - ```bash |
83 | | - ./target/example-default \ |
84 | | - org.graalvm.example.action.StringReverser reverse "hello" |
85 | | - ``` |
| 91 | +``` |
| 92 | +HELLO |
| 93 | +``` |
| 94 | + |
| 95 | +## Build a Native Image |
| 96 | + |
| 97 | +The project uses the Native Build Tools Maven plugin to drive `native-image`. |
| 98 | +The `native-default` profile produces an executable without additional |
| 99 | +reflection configuration. |
86 | 100 |
|
87 | | - You will see a `ClassNotFoundException` similar to: |
| 101 | +1. Build: |
88 | 102 |
|
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 | | - ``` |
| 103 | +```bash |
| 104 | +./mvnw package -Pnative-default |
| 105 | +``` |
99 | 106 |
|
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. |
| 107 | +2. Run: |
101 | 108 |
|
102 | | -## Native Image Using `-H:Preserve` |
| 109 | +```bash |
| 110 | +./target/example-default \ |
| 111 | + org.graalvm.example.action.StringReverser reverse "hello" |
| 112 | +``` |
103 | 113 |
|
104 | | -GraalVM for JDK 25 introduces the `-H:Preserve` option. |
| 114 | +You should see a `ClassNotFoundException` similar to: |
105 | 115 |
|
106 | | -This option lets you instruct the `native-image` tool to keep entire packages, modules, or all classes on the classpath. |
| 116 | +``` |
| 117 | +Exception in thread "main" java.lang.ClassNotFoundException: org.graalvm.example.action.StringReverser |
| 118 | + at org.graalvm.nativeimage.builder/com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:339) |
| 119 | + ... |
| 120 | + at org.graalvm.example.ReflectionExample.main(ReflectionExample.java:56) |
| 121 | +``` |
107 | 122 |
|
108 | | -<!-- This can result in larger application size. --> |
| 123 | +This happens because static analysis did not discover that `StringReverser` (and |
| 124 | +`StringCapitalizer`) are used via reflection, so they were not included. |
109 | 125 |
|
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. |
| 126 | +## Identify Dynamic Access With the Build Report |
112 | 127 |
|
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: |
| 128 | +GraalVM 25 adds an experimental reporting option to help you find dynamic access |
| 129 | +before it breaks at runtime. With `-H:+ReportDynamicAccess`, in conjunction with |
| 130 | +`--emit=build-report`, the Native Image [build |
| 131 | +report](https://www.graalvm.org/latest/reference-manual/native-image/overview/build-report/) |
| 132 | +highlights reflective usage present in the image. |
| 133 | + |
| 134 | +The `native-default` profile already enables this feature: |
116 | 135 |
|
117 | 136 | ```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> |
| 137 | +<buildArgs> |
| 138 | + <buildArg>-H:+UnlockExperimentalVMOptions</buildArg> |
| 139 | + <buildArg>-H:+ReportDynamicAccess</buildArg> |
| 140 | + <buildArg>--emit=build-report</buildArg> |
| 141 | +</buildArgs> |
125 | 142 | ``` |
126 | 143 |
|
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: |
| 144 | +After building, open the report, `target/example-default-build-report.html`, and navigate to the “Dynamic Access” tab to review reflection usage in `ReflectionExample#main`. |
| 145 | + |
| 146 | + |
| 147 | +The report highlights code that needs to be reviewed to ensure successful |
| 148 | +runtime execution of the application. In this application, the classes loaded |
| 149 | +via `Class.forName(...)` need to be included in the executable. |
| 150 | + |
| 151 | + |
128 | 152 |
|
129 | | - ```shell |
130 | | - ./mvnw package -Pnative-preserve |
131 | | - ``` |
| 153 | +## Fix It With ‘-H:Preserve’ (Experimental) |
| 154 | + |
| 155 | +GraalVM 25 introduces the experimental `-H:Preserve` option to keep entire |
| 156 | +packages, modules, or all classes on the classpath. |
| 157 | + |
| 158 | +In this project, both action classes are in the `org.graalvm.example.action` |
| 159 | +package so `package=org.graalvm.example.action` can be added to the |
| 160 | +`-H:Preserve` option. The Maven `native-preserve` profile adds the necessary |
| 161 | +`native-image` command line args: |
| 162 | + |
| 163 | +```xml |
| 164 | +<buildArgs> |
| 165 | + <buildArg>-H:+UnlockExperimentalVMOptions</buildArg> |
| 166 | + <buildArg>-H:Preserve=package=org.graalvm.example.action</buildArg> |
| 167 | +</buildArgs> |
| 168 | +``` |
132 | 169 |
|
133 | | -2. Run the new `example-preserve` executable to confirm the previously missing `StringReverser` class and its methods are now included: |
| 170 | +1. Build with preserve: |
134 | 171 |
|
135 | | - ```shell |
136 | | - ./target/example-preserve \ |
137 | | - org.graalvm.example.action.StringReverser reverse "hello" |
138 | | - ``` |
| 172 | +```bash |
| 173 | +./mvnw package -Pnative-preserve |
| 174 | +``` |
139 | 175 |
|
140 | | - Expected output: |
| 176 | +2. Run `StringReverser`: |
141 | 177 |
|
142 | | - ```shell |
143 | | - olleh |
144 | | - ``` |
| 178 | +```bash |
| 179 | +./target/example-preserve \ |
| 180 | + org.graalvm.example.action.StringReverser reverse "hello" |
| 181 | +``` |
145 | 182 |
|
146 | | -3. Run the executable to confirm the `StringCapitalizer` class works too: |
| 183 | +Expected output: |
147 | 184 |
|
148 | | - ```shell |
149 | | - ./target/example-preserve \ |
150 | | - org.graalvm.example.action.StringCapitalizer capitalize "hello" |
151 | | - ``` |
| 185 | +``` |
| 186 | +olleh |
| 187 | +``` |
152 | 188 |
|
153 | | - Expected output: |
| 189 | +3. Run `StringCapitalizer`: |
154 | 190 |
|
155 | | - ```shell |
156 | | - HELLO |
157 | | - ``` |
| 191 | +```bash |
| 192 | +./target/example-preserve \ |
| 193 | + org.graalvm.example.action.StringCapitalizer capitalize "hello" |
| 194 | +``` |
158 | 195 |
|
159 | | -As demonstrated, `-H:Preserve` provides an easy way to ensure that Native Image includes classes not discovered by static analysis. |
| 196 | +Expected output: |
160 | 197 |
|
161 | | -### Related Documentation |
| 198 | +``` |
| 199 | +HELLO |
| 200 | +``` |
162 | 201 |
|
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) |
| 202 | +As shown: |
| 203 | +- `-H:+ReportDynamicAccess` helps identify code paths involving reflection. |
| 204 | +- `-H:Preserve` makes it straightforward to include the required classes when |
| 205 | + static analysis alone cannot find them. |
| 206 | + |
| 207 | +## Tips |
| 208 | + |
| 209 | +- Always include `-H:+UnlockExperimentalVMOptions` when using experimental |
| 210 | + options like `-H:Preserve` or `-H:+ReportDynamicAccess`. |
| 211 | +- If you maintain larger apps, start with the dynamic access report to scope |
| 212 | + what needs preserving, then apply `-H:Preserve` to the smallest package(s) |
| 213 | + that cover your use cases. |
| 214 | +- For fine-grained control or for libraries you don’t own, consider JSON |
| 215 | + reachability metadata as a complement or alternative. |
| 216 | + |
| 217 | +## Related Documentation |
| 218 | + |
| 219 | +- Reachability Metadata (Reflection): |
| 220 | + https://www.graalvm.org/latest/reference-manual/native-image/metadata/ |
| 221 | +- Assisted Configuration with Tracing Agent: |
| 222 | + https://www.graalvm.org/latest/reference-manual/native-image/metadata/AutomaticMetadataCollection/#tracing-agent |
| 223 | +- Java Reflection API (JDK 25): |
| 224 | + https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/reflect/package-summary.html |
| 225 | + - Native Image Build Report: https://www.graalvm.org/latest/reference-manual/native-image/overview/build-report/ |
0 commit comments