GraalVM Native Image is a native executable with optimized translation of Java application which includes only the core required at run time. The executable has several advantages, such as
- Faster startup times
- (Usually) smaller package and heap size
Vert.x is a tool-kit for building reactive Java application rather than full-features framework. Thus, Vert.x is suitable candidate to build native image as native image compilation has several restrictions which most not apply to Vert.x core code.
Below are the steps on how to build GraalVM Native Image with Vert.x and Docker.
Table Of Contents
Steps
Following are the list of tools need for the project:
- Docker
- Maven (or Gradle)
1. Create a new Vert.x application
Create a new Vert.x applicaton using Vert.x App Generator.
Make note the Group Id and Artifact Id when create the application. In this example, following are the values used for both:
- Group Id: com.anasdidi
- Artifact Id: nativeimage
Figure 01: Vert.x Starter - Create a new Vert.x application
Then, create a simple Hello World Vert.x application.
package com.anasdidi.nativeimage;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
public class MainVerticle extends AbstractVerticle {
@Override
public void start(Promise<Void> startPromise) throws Exception {
vertx.createHttpServer().requestHandler(req -> {
req.response()
.putHeader("content-type", "text/plain")
.end("Hello from Vert.x!");
}).listen(8888, http -> {
if (http.succeeded()) {
startPromise.complete();
System.out.println("HTTP server started on port 8888");
} else {
startPromise.fail(http.cause());
}
});
}
}
2. Setup GraalVM config files
Create a new folder in path:
src/main/resources/META-INF/native-image/<Group Id>/<Artifact Id>
.
In this case, the created folder will be:
src/main/resources/META-INF/native-image/com.anasdidi/nativeimage.
Next, create these files in the folder:
- jni-config.json
- native-image.properties
- reflect-config.json
- resource-config.json
[]
META-INF/native-image/com.anasdidi/nativeimage/jni-config.json
Args =\
--enable-http \
--enable-https \
-H:EnableURLProtocols=http,https \
--report-unsupported-elements-at-runtime \
-H:ReflectionConfigurationResources=${.}/reflect-config.json \
-H:JNIConfigurationResources=${.}/jni-config.json \
-H:ResourceConfigurationResources=${.}/resource-config.json \
--initialize-at-run-time=io.netty.handler.codec.compression.ZstdOptions \
-H:+PrintClassInitialization \
-H:+ReportExceptionStackTraces
META-INF/native-image/com.anasdidi/nativeimage/native-image.properties
[
{
"name": "io.vertx.core.impl.launcher.commands.RunCommand",
"allDeclaredConstructors": true,
"allDeclaredMethods": true
},
{
"name": "io.vertx.core.impl.launcher.commands.VertxIsolatedDeployer",
"allDeclaredConstructors": true,
"allDeclaredMethods": true
},
{
"name": "java.lang.Long",
"allDeclaredConstructors": true
},
{
"name": "java.lang.Integer",
"allDeclaredConstructors": true
},
{
"name": "com.anasdidi.nativeimage.MainVerticle",
"allDeclaredConstructors": true,
"allDeclaredMethods": true
}
]
META-INF/native-image/com.anasdidi/nativeimage/reflect-config.json
Replace "name": "com.anasdidi.nativeimage.MainVerticle"
with respective Group Id and Artifact Id.
{
"resources": {
"includes": [{ "pattern": "META-INF/com.anasdidi.nativeimage.*" }],
"excludes": []
}
}
META-INF/native-image/com.anasdidi/nativeimage/resource-config.json
Replace {"pattern": "META-INF/com.anasdidi.nativeimage.*"}
with respective Group Id and Artifact Id.
Below are the structure of the folders and files for the setup.
├── pom.xml
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── anasdidi
│ │ │ └── nativeimage
│ │ │ └── MainVerticle.java
│ │ └── resources
│ │ └── META-INF
│ │ └── native-image
│ │ └── com.anasdidi
│ │ └── nativeimage
│ │ ├── jni-config.json
│ │ ├── native-image.properties
│ │ ├── reflect-config.json
│ │ └── resource-config.json
3. Create Dockerfile
Create a multi-stage builds Dockerfile to create the Docker image. The multi-stage consists of following stages:
- Compile the source code to Jar file
- Build the Jar file to GraalVM native image
- Run the native image
FROM maven:3.8.5-openjdk-17-slim as build-jar
WORKDIR /workspace
COPY src/ /workspace/src/
COPY pom.xml /workspace/pom.xml
RUN mvn clean package -DskipTests
Stage 1: Compile the source code to Jar file
FROM ghcr.io/graalvm/native-image:ol8-java17-22.3.3 as build-nativeimage
WORKDIR /workspace
COPY /workspace/target/nativeimage-1.0.0-SNAPSHOT-fat.jar /workspace/nativeimage-1.0.0-SNAPSHOT-fat.jar
RUN native-image --static -jar nativeimage-1.0.0-SNAPSHOT-fat.jar nativeimage
Stage 2: Build the Jar file to GraalVM native image
FROM alpine:3.18.3
WORKDIR /workspace
COPY /workspace/nativeimage /workspace/nativeimage
EXPOSE 8888
ENTRYPOINT ["sh", "-c"]
CMD ["exec ./nativeimage run com.anasdidi.nativeimage.MainVerticle"]
Stage 3: Run the native image
Below is the complete the Dockefile.
FROM maven:3.8.5-openjdk-17-slim as build-jar
WORKDIR /workspace
COPY src/ /workspace/src/
COPY pom.xml /workspace/pom.xml
RUN mvn clean package -DskipTests
FROM ghcr.io/graalvm/native-image:ol8-java17-22.3.3 as build-nativeimage
WORKDIR /workspace
COPY /workspace/target/nativeimage-1.0.0-SNAPSHOT-fat.jar /workspace/nativeimage-1.0.0-SNAPSHOT-fat.jar
RUN native-image --static -jar nativeimage-1.0.0-SNAPSHOT-fat.jar nativeimage
FROM alpine:3.18.3
WORKDIR /workspace
COPY /workspace/nativeimage /workspace/nativeimage
EXPOSE 8888
ENTRYPOINT ["sh", "-c"]
CMD ["exec ./nativeimage run com.anasdidi.nativeimage.MainVerticle"]
Dockerfile
4. Build and run Docker image
Next, execute the Docker build command to build the image using the Dockerfile.
docker build -t test -f nativeimage.Dockerfile
Docker build command
After done, execute the Docker run command run the image.
podman run -p 8888:8888 test
Docker run command
Press CTRL + C
to stop the Docker image.
In case the Docker image unable to stop using keys, stop the Docker image using Docker stop command.
$ podman ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1efbfd8f9841 localhost/test:latest exec ./nativeimag... 4 minutes ago Up 4 minutes 0.0.0.0:8888->8888/tcp magical_clarke
Docker ps command to get running container ID. In this case, it is 1efbfd8f9841.
podman container stop 1efbfd8f9841
Docker container command to stop container using container ID