Week 153 — What is the native-image tool and what is it used for?

Question of the Week #153
What is the native-image tool and what is it used for?
2 Replies
MrMisha | Coder
GraalVM comes with a tool named native-image that can be used to compile Java applications to platform-dependent native executables. These executables typically have a lower memory footprint and faster startup time but this comes at the cost of longer build times and needing to specify reflectively accessed elements (as well as elements used with certain other features such as JNI) at build time instead of being able to access any class in the classpath/modulepath at runtime. Let's say we have a simple hello world application in a file named Hello.java:
void main() {
IO.println("Hello World");
}
void main() {
IO.println("Hello World");
}
This file can be compiled using javac Hello.java and run with java Hello (assuming JDK 25). With GraalVM, it is also possible to convert the compiled bytecode (output of javac Hello.java) to a native executable using the command native-image Hello. This executable can then be executed using ./hello (or .\hello.exe on Windows). It is also possible to do the same with JAR files using native-image -jar Hello.jar (assuming the JAR file is named Hello.jar). In general, the native-image command shares many options with the java command. When the native-image tool is used to compile a Java application to a native executable, it performs a "closed-world" analysis of the whole application including the used dependencies and the JDK itself. It looks through all used classes/methods and eleminates any code that is not used. However, this is in conflict with the idea of reflection and JNI which allow introspecting/resolving and accssing Java elements (like classes, methods and fields) at runtime. To solve that problem, the accessed elements have to be specified in reachability metadata files. These files should contain information about types that are accessed reflectively, via JNI or via serialization as well as accessed classpath/modulepath resources and resource bundles. This file is typically located in META-INF/native-image/<groupId>/<artifactId>/reachability-metadata.json and can look similar to the following:
{
"reflection": [
{
"type": "com.example.myproject.MyClassThatIsAccessedViaReflection",
"methods": [
{
"name": "someMethod",
"parameterTypes": [
"java.util.List",
"com.example.myproject.OfMyParameterTypes"
]
}
],
"fields": [
{
"name": "someField"
}
],
"allPublicConstructors": true
}
]
}
{
"reflection": [
{
"type": "com.example.myproject.MyClassThatIsAccessedViaReflection",
"methods": [
{
"name": "someMethod",
"parameterTypes": [
"java.util.List",
"com.example.myproject.OfMyParameterTypes"
]
}
],
"fields": [
{
"name": "someField"
}
],
"allPublicConstructors": true
}
]
}
For example, consider the following program:
import java.lang.reflect.Method;

public class MyApp {
void main() throws Exception {
Class<?> cl = MyApp.class.getClassLoader().loadClass("SomeClass");
for(Method method : cl.getDeclaredMethods()) {
IO.println(method.getName());
Object instance = cl.getDeclaredConstructor().newInstance();
cl.getField("a").set(instance, 37);
if(method.getParameterTypes().length == 1 && method.getParameterTypes()[0] == String.class) {
method.invoke(instance, "value: ");
}
}
}
}
class SomeClass {
public int a = 13;
public void hi(String s) {
IO.println(s + a);
}
}
import java.lang.reflect.Method;

public class MyApp {
void main() throws Exception {
Class<?> cl = MyApp.class.getClassLoader().loadClass("SomeClass");
for(Method method : cl.getDeclaredMethods()) {
IO.println(method.getName());
Object instance = cl.getDeclaredConstructor().newInstance();
cl.getField("a").set(instance, 37);
if(method.getParameterTypes().length == 1 && method.getParameterTypes()[0] == String.class) {
method.invoke(instance, "value: ");
}
}
}
}
class SomeClass {
public int a = 13;
public void hi(String s) {
IO.println(s + a);
}
}
If this class is run normally, it prints the following:
hi
value: 37
hi
value: 37
However, running it via native-image causes a ClassNotFoundException because there is no reflection metadata marking that class to be reflectively accessed.
MrMisha | Coder
If we want to make sure the class is accessible, we could provide a META-INF/native-image/com.example/myapp/reachability-metadata.json file with the following content:
{
"reflection": [
{
"type": "SomeClass",
"methods": [
{
"name": "hi",
"parameterTypes": [
"java.lang.String"
]
},
{
"name": "<init>",
"parameterTypes": []
}
],
"fields": [
{
"name": "a"
}
]
}
]
}
{
"reflection": [
{
"type": "SomeClass",
"methods": [
{
"name": "hi",
"parameterTypes": [
"java.lang.String"
]
},
{
"name": "<init>",
"parameterTypes": []
}
],
"fields": [
{
"name": "a"
}
]
}
]
}
If we then create the native executable using native-image MyApp, we get the same output as before using java MyApp:
hi
value: 37
hi
value: 37
📖 Sample answer from dan1st

Did you find this page helpful?