Skip to content

Commit 64ae44f

Browse files
committed
Update for GraalVM 25, added -H:ReportDynamicAccess
1 parent f223a8d commit 64ae44f

File tree

2 files changed

+186
-169
lines changed

2 files changed

+186
-169
lines changed
Lines changed: 160 additions & 167 deletions
Original file line numberDiff line numberDiff line change
@@ -1,226 +1,219 @@
1-
# Using Native Image `Preserve` Option
1+
# GraalVM Native Image: Using the -H:Preserve option
22

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.
148

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.
1712

18-
## Preparation
13+
## Prerequisites
1914

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)
2218

23-
```shell
24-
sdk install java 25.ea.34-graal
25-
```
19+
Install GraalVM 25 EA with SDKMAN!:
2620

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+
```
2932

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
3434

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.
3639

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:
4041

4142
```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);
4546
```
4647

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)`
4951

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.
5153

52-
```shell
53-
./mvnw package
54-
```
54+
### Run on the JVM
5555

56-
2. Run `ReflectionExample` (the JAR entry point) to invoke the `StringReverser` action:
56+
1) Build the JAR:
5757

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+
```
6261

63-
Expected output:
62+
2) Invoke `StringReverser`:
6463

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+
```
6868

69-
3. Run the same command for the `StringCapitalizer` action:
69+
Expected output:
7070

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+
```
7574

76-
Expected output:
75+
3) Invoke `StringCapitalizer`:
7776

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+
```
8181

82-
## GraalVM Native Image
82+
Expected output:
8383

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+
```
8987

90-
1. Build a native executable using the `native-default` profile (see the [_pom.xml_](pom.xml) file):
88+
## Build a native image
9189

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.
9593

96-
2. Run the resulting `example-default` native executable:
94+
1) Build:
9795

98-
```bash
99-
./target/example-default \
100-
org.graalvm.example.action.StringReverser reverse "hello"
101-
```
96+
```bash
97+
./mvnw package -Pnative-default
98+
```
10299

103-
You will see a `ClassNotFoundException` similar to:
100+
2) Run:
104101

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+
```
115106

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:
119108

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.
121118

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
128120

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:
130127

131128
```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>
139134
```
140135

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:
150137

151-
![Native Image Build Report-Dynamic Access](build-report.jpeg)
138+
- `target/example-default-build-report.html`
139+
- See the “Dynamic Access” tab to review reflection usage in
140+
`ReflectionExample#main`.
152141

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.
154145

155-
To make it easy to ensure required classes are included in an executable,
156-
GraalVM 25 introduced the experimental `-H:Preserve` option.
146+
![Native Image Build Report - Dynamic Access](build-report.jpeg)
157147

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)
160149

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.
165152

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:
171157

172158
```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>
180163
```
181164

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:
185166

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+
```
197170

198-
Expected output:
171+
2) Run `StringReverser`:
199172

200-
```shell
201-
olleh
202-
```
173+
```bash
174+
./target/example-preserve \
175+
org.graalvm.example.action.StringReverser reverse "hello"
176+
```
203177

204-
3. Run the executable to confirm the `StringCapitalizer` class works too:
178+
Expected output:
205179

206-
```shell
207-
./target/example-preserve \
208-
org.graalvm.example.action.StringCapitalizer capitalize "hello"
209-
```
180+
```
181+
olleh
182+
```
210183

211-
Expected output:
184+
3) Run `StringCapitalizer`:
212185

213-
```shell
214-
HELLO
215-
```
186+
```bash
187+
./target/example-preserve \
188+
org.graalvm.example.action.StringCapitalizer capitalize "hello"
189+
```
216190

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:
221192

222-
### Related Documentation
193+
```
194+
HELLO
195+
```
223196

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

Comments
 (0)