In this blog, I am going to look at Spring Boot Release 3.2 for building and generating native image. There are two possible ways in Spring boot to generate native images: by using Spring build packs & GraalVM native tool. The first option uses a Docker environment to maintain the native image, and you have to run the image as a container. With the second option, you are using GraalVM to generate the image and can run it as a plain executable application on your host machine.
Native Image is a technology to ahead-of-time compile Java code to a standalone executable, called a native image. This executable includes the application classes, classes from its dependencies, runtime library classes, and statically linked native code from JDK. It does not run on the Java VM, but includes necessary components like memory management, thread scheduling, and so on from a different runtime system, called “Substrate VM”.
So, we are going to do the following:
Build a simple cloud-native application based on Spring Boot.
Next, we will generate a Native image by using Spring buildpacks and GraalVM Native build tool
Run the native image.
The entire project will heavily depend on the following dependencies:
Spring Boot 3.2.0
Docker desktop
Open JDK 21. 1.0
GraalVM 21.0.2
GraalVM build tool maven plugin
Let's get our hands dirty. As my reader already knows, I am always trying to write blog post that are 100% comprehensible, and therefore you can find the example project on GitHub.
Step 1. Build a basic microservice named NativeImageTestApplication.
You can build the project from scratch or just clone it from the Github repositories. The application has only one REST service named 'getAllCustomers', which will return 3 customers in JSON format. The fragment of the application is shown below:
@RestController
class CustomersHttpController {
@GetMapping("/customers")
Collection<Customer> getAllCustomers() {
LOG.info("Invoked method: {getAllCustomers} ");
return Set.of(new Customer(1, "A"), new Customer(2, "B"), new Customer(3, "C"));
}
record Customer(Integer id, String name) {
}
}
The REST service will be available at http://HOST:8080/customers
The only interesting part of the application is the Maven plugin for generating the image:
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
</plugin>
Step 2. Build and test the Spring Boot application.
To build the project, run the following command from your favourite terminal:.
mvn clean install
At this point, the project is ready to run. Execute the following command from the project home directory:.
mvn spring-boot:run
You should see output similar to the following:
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.2.0)
2024-04-04T17:31:11.096+03:00 INFO 11533 --- [ main] c.b.r.n.NativeImageTestApplication : Starting NativeImageTestApplication using Java 21.0.2 with PID 11533 (/Users/shamim/Development/workshop/github/Developing_Reactive_app/21issues/native-image-test/target/classes started by shamim in /Users/shamim/Development/workshop/github/Developing_Reactive_app/21issues/native-image-test)
2024-04-04T17:31:11.098+03:00 INFO 11533 --- [ main] c.b.r.n.NativeImageTestApplication : No active profile set, falling back to 1 default profile: "default"
2024-04-04T17:31:11.932+03:00 INFO 11533 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
2024-04-04T17:31:11.953+03:00 INFO 11533 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2024-04-04T17:31:11.953+03:00 INFO 11533 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.16]
2024-04-04T17:31:12.022+03:00 INFO 11533 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2024-04-04T17:31:12.023+03:00 INFO 11533 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 885 ms
2024-04-04T17:31:12.351+03:00 INFO 11533 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path ''
2024-04-04T17:31:12.357+03:00 INFO 11533 --- [ main] c.b.r.n.NativeImageTestApplication : Started NativeImageTestApplication in 1.609 seconds (process running for 1.917)
Note that the application started in 1.609 seconds.
Open another terminal and run the following URL:. Alternatively, you can open a web browser at http://localhost:8080/customers .
curl http://HOST:8080/customers
You should see the following output:
[
{
"id": 2,
"name": "B"
},
{
"id": 3,
"name": "C"
},
{
"id": 1,
"name": "A"
}
]
Option 1. Build a native image with Spring buildpacks
In this option, with a single Maven command, you will get a Docker image on your local Docker daemon. The image will not contain any JVM rather than static binary to run on the Docker daemon directly.
So, you need a Docker desktop up and running on your host machine. If you don't have any Docker daemons installed, please refer to the Docker site to download and install them.
Now, build the image with the native
profile active
mvn -Pnative spring-boot:build-image
Wait for a while. It can take up to 2 minutes to generate and upload the image to your local Docker repository.
[INFO] Successfully built image 'docker.io/library/native-image-test:0.0.1-SNAPSHOT'
You can find the image in the Docker local repository, as shown below:
Note that the Image size (114 MB) is bigger than the regular Jar file. At this moment, you can run the Docker container for the native-image-test. Apply the following command in your terminal:.
docker run --rm -p 8080:8080 native-image-test:0.0.1-SNAPSHOT
The application should start almost instantaneously. In my case, the startup time is 0.374 seconds.
❯ docker run --rm -p 8080:8080 native-image-test:0.0.1-SNAPSHOT
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.2.0)
2024-04-04T14:55:13.011Z INFO 1 --- [ main] c.b.r.n.NativeImageTestApplication : Starting AOT-processed NativeImageTestApplication using Java 21.0.2 with PID 1 (/workspace/com.blu.reactive.nativeimagetest.NativeImageTestApplication started by cnb in /workspace)
2024-04-04T14:55:13.012Z INFO 1 --- [ main] c.b.r.n.NativeImageTestApplication : No active profile set, falling back to 1 default profile: "default"
2024-04-04T14:55:13.097Z INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
2024-04-04T14:55:13.107Z INFO 1 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2024-04-04T14:55:13.107Z INFO 1 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.16]
2024-04-04T14:55:13.149Z INFO 1 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2024-04-04T14:55:13.149Z INFO 1 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 137 ms
2024-04-04T14:55:13.243Z INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path ''
2024-04-04T14:55:13.244Z INFO 1 --- [ main] c.b.r.n.NativeImageTestApplication : Started NativeImageTestApplication in 0.374 seconds (process running for 0.616)
To test the service, create a REST request on the service route's http://HOST:8080/customers endpoint.
Option 2. Using the GraalVM Native Build Tool.
For these options, you have to download and install Java GraalVM. If you are using SDK Manager like me, do the following:
sdk install java 21.0.2-graalce
sdk use 21.0.2-graalce
Check the Java version, as follows:
java --version
You should get a similar output, as shown below:
openjdk 21.0.2 2024-01-16
OpenJDK Runtime Environment GraalVM CE 21.0.2+13.1 (build 21.0.2+13-jvmci-23.1-b30)
OpenJDK 64-Bit Server VM GraalVM CE 21.0.2+13.1 (build 21.0.2+13-jvmci-23.1-b30, mixed mode, sharing)
Now, let's generate a native image with the profile '-Pnative'. Apply the following command into terminal.
mvn -Pnative native:compile
The command will take a while to complete. After successful generation, the native image executable file will be found in the 'targer'
directory. In my case, the name of the native-image is 'native-image-test'
with 80,9 MB file size. Slightly smaller than the Docker image but larger than the Java Jar file.
Run the executable file.
./target/native-image-test
The application started almost instantly.
❯ ./target/native-image-test
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.2.0)
2024-04-04T18:07:18.834+03:00 INFO 12028 --- [ main] c.b.r.n.NativeImageTestApplication : Starting AOT-processed NativeImageTestApplication using Java 21.0.2 with PID 12028 (/Users/shamim/Development/workshop/github/Developing_Reactive_app/21issues/native-image-test/target/native-image-test started by shamim in /Users/shamim/Development/workshop/github/Developing_Reactive_app/21issues/native-image-test)
2024-04-04T18:07:18.834+03:00 INFO 12028 --- [ main] c.b.r.n.NativeImageTestApplication : No active profile set, falling back to 1 default profile: "default"
2024-04-04T18:07:18.851+03:00 INFO 12028 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
2024-04-04T18:07:18.851+03:00 INFO 12028 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2024-04-04T18:07:18.852+03:00 INFO 12028 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.16]
2024-04-04T18:07:18.862+03:00 INFO 12028 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2024-04-04T18:07:18.862+03:00 INFO 12028 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 28 ms
2024-04-04T18:07:18.894+03:00 INFO 12028 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path ''
2024-04-04T18:07:18.894+03:00 INFO 12028 --- [ main] c.b.r.n.NativeImageTestApplication : Started NativeImageTestApplication in 0.078 seconds (process running for 0.09)
The startup time is 0.078 seconds.
Let's summarise our small benchmark:
JVM startup time: 1.609 seconds
Docker container startup time: 0.374 seconds.
Native executable startup time: 0.078 seconds
In this blog post, we learned about the two options provided by the Spring Boot community to generate or build Java application native images using the Maven build tool.
References:
GraalVM Native Image Support https://docs.spring.io/spring-boot/docs/current/reference/html/native-image.html