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