Skip to content

Commit 6b446b3

Browse files
authored
Merge pull request #360 from graalvm/Preserve_add_ReportDynamicAccess
Preserve demo: add report dynamic access
2 parents aa6c70f + 8a04b25 commit 6b446b3

File tree

4 files changed

+205
-114
lines changed

4 files changed

+205
-114
lines changed
Lines changed: 172 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,165 +1,225 @@
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 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.
77

8-
This guide demonstrates how to declare reflection configuration using the `-H:Preserve` option.
8+
## Introduction
99

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

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

14-
```shell
15-
sdk install java 25.ea.29-graal
16-
```
20+
## Prerequisites
1721

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

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!:
2427

25-
## Example Using Reflection on the JVM
28+
```bash
29+
sdk install java 25-graal
30+
sdk use java 25-graal
31+
```
2632

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

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
3338
```
3439

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
3641

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

39-
```shell
40-
./mvnw package
41-
```
47+
The core code is:
4248

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

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

50-
Expected output:
59+
This works on the JVM as long as those classes are on the classpath.
5160

52-
```shell
53-
olleh
54-
```
61+
### Run on the JVM
5562

56-
3. Run the same command for the `StringCapitalizer` action:
63+
1. Build the JAR:
5764

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

63-
Expected output:
69+
2. Invoke `StringReverser`:
6470

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

69-
## GraalVM Native Image
76+
Expected output:
7077

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

74-
1. Build a native executable using the `native-default` profile (see the [_pom.xml_](pom.xml) file):
82+
3. Invoke `StringCapitalizer`:
7583

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

80-
2. Run the resulting `example-default` native executable:
89+
Expected output:
8190

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

87-
You will see a `ClassNotFoundException` similar to:
101+
1. Build:
88102

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

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

102-
## Native Image Using `-H:Preserve`
109+
```bash
110+
./target/example-default \
111+
org.graalvm.example.action.StringReverser reverse "hello"
112+
```
103113

104-
GraalVM for JDK 25 introduces the `-H:Preserve` option.
114+
You should see a `ClassNotFoundException` similar to:
105115

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

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

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
112127

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

117136
```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>
125142
```
126143

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+
![Native Image Build Report - Dynamic Access](build-report.jpeg)
128152

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

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

135-
```shell
136-
./target/example-preserve \
137-
org.graalvm.example.action.StringReverser reverse "hello"
138-
```
172+
```bash
173+
./mvnw package -Pnative-preserve
174+
```
139175

140-
Expected output:
176+
2. Run `StringReverser`:
141177

142-
```shell
143-
olleh
144-
```
178+
```bash
179+
./target/example-preserve \
180+
org.graalvm.example.action.StringReverser reverse "hello"
181+
```
145182

146-
3. Run the executable to confirm the `StringCapitalizer` class works too:
183+
Expected output:
147184

148-
```shell
149-
./target/example-preserve \
150-
org.graalvm.example.action.StringCapitalizer capitalize "hello"
151-
```
185+
```
186+
olleh
187+
```
152188

153-
Expected output:
189+
3. Run `StringCapitalizer`:
154190

155-
```shell
156-
HELLO
157-
```
191+
```bash
192+
./target/example-preserve \
193+
org.graalvm.example.action.StringCapitalizer capitalize "hello"
194+
```
158195

159-
As demonstrated, `-H:Preserve` provides an easy way to ensure that Native Image includes classes not discovered by static analysis.
196+
Expected output:
160197

161-
### Related Documentation
198+
```
199+
HELLO
200+
```
162201

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/
136 KB
Loading

native-image/preserve-package/pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@
3737
<configuration>
3838
<mainClass>org.graalvm.example.ReflectionExample</mainClass>
3939
<imageName>example-default</imageName>
40+
<buildArgs>
41+
<buildArg>-H:+UnlockExperimentalVMOptions</buildArg>
42+
<buildArg>-H:+ReportDynamicAccess</buildArg>
43+
<buildArg>--emit=build-report</buildArg>
44+
</buildArgs>
4045
</configuration>
4146
</plugin>
4247
</plugins>
@@ -66,6 +71,8 @@
6671
<buildArgs>
6772
<buildArg>-H:+UnlockExperimentalVMOptions</buildArg>
6873
<buildArg>-H:Preserve=package=org.graalvm.example.action</buildArg>
74+
<buildArg>-H:+ReportDynamicAccess</buildArg>
75+
<buildArg>--emit=build-report</buildArg>
6976
</buildArgs>
7077
</configuration>
7178
</plugin>

0 commit comments

Comments
 (0)