Developing Your First GraalVM Native Application
There are two main ways to build a Spring Boot native image application:
-
Using Spring Boot support for Cloud Native Buildpacks with the Paketo Java Native Image buildpack to generate a lightweight container containing a native executable.
-
Using GraalVM Native Build Tools to generate a native executable.
The easiest way to start a new native Spring Boot project is to go to start.spring.io, add the GraalVM Native Support dependency and generate the project.
The included HELP.md file will provide getting started hints.
|
Sample Application
We need an example application that we can use to create our native image. For our purposes, the simple "Hello World!" web application that’s covered in the Developing Your First Spring Boot Application section will suffice.
To recap, our main application code looks like this:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@SpringBootApplication
public class MyApplication {
@RequestMapping("/")
String home() {
return "Hello World!";
}
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
This application uses Spring MVC and embedded Tomcat, both of which have been tested and verified to work with GraalVM native images.
Building a Native Image Using Buildpacks
Spring Boot supports building Docker images containing native executables, using Cloud Native Buildpacks (CNB) integration with both Maven and Gradle and the Paketo Java Native Image buildpack. This means you can just type a single command and quickly get a sensible image into your locally running Docker daemon. The resulting image doesn’t contain a JVM, instead the native image is compiled statically. This leads to smaller images.
The CNB builder used for the images is paketobuildpacks/builder-jammy-java-tiny:latest .
It has small footprint and reduced attack surface, but you can also use paketobuildpacks/builder-jammy-base:latest or paketobuildpacks/builder-jammy-full:latest to have more tools available in the image if required.
|
System Requirements
Docker should be installed. See Get Docker for more details. Configure it to allow non-root user if you are on Linux.
You can run docker run hello-world (without sudo ) to check the Docker daemon is reachable as expected.
Check the Maven or Gradle Spring Boot plugin documentation for more details.
|
On macOS, it is recommended to increase the memory allocated to Docker to at least 8GB , and potentially add more CPUs as well.
See this Stack Overflow answer for more details.
On Microsoft Windows, make sure to enable the Docker WSL 2 backend for better performance.
|
Using Maven
To build a native image container using Maven you should ensure that your pom.xml
file uses the spring-boot-starter-parent
and the org.graalvm.buildtools:native-maven-plugin
.
You should have a <parent>
section that looks like this:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.0</version>
</parent>
You additionally should have this in the <build> <plugins>
section:
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
</plugin>
The spring-boot-starter-parent
declares a native
profile that configures the executions that need to run in order to create a native image.
You can activate profiles using the -P
flag on the command line.
If you don’t want to use spring-boot-starter-parent you’ll need to configure executions for the process-aot goal from Spring Boot’s plugin and the add-reachability-metadata goal from the Native Build Tools plugin.
|
To build the image, you can run the spring-boot:build-image
goal with the native
profile active:
$ mvn -Pnative spring-boot:build-image
Using Gradle
The Spring Boot Gradle plugin automatically configures AOT tasks when the GraalVM Native Image plugin is applied.
You should check that your Gradle build contains a plugins
block that includes org.graalvm.buildtools.native
.
As long as the org.graalvm.buildtools.native
plugin is applied, the bootBuildImage
task will generate a native image rather than a JVM one.
You can run the task using:
$ gradle bootBuildImage
Running the example
Once you have run the appropriate build command, a Docker image should be available.
You can start your application using docker run
:
$ docker run --rm -p 8080:8080 docker.io/library/myproject:0.0.1-SNAPSHOT
You should see output similar to the following:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v{version-spring-boot})
....... . . .
....... . . . (log output here)
....... . . .
........ Started MyApplication in 0.08 seconds (process running for 0.095)
The startup time differs from machine to machine, but it should be much faster than a Spring Boot application running on a JVM. |
If you open a web browser to localhost:8080
, you should see the following output:
Hello World!
To gracefully exit the application, press ctrl-c
.
Building a Native Image using Native Build Tools
If you want to generate a native executable directly without using Docker, you can use GraalVM Native Build Tools. Native Build Tools are plugins shipped by GraalVM for both Maven and Gradle. You can use them to perform a variety of GraalVM tasks, including generating a native image.
Prerequisites
To build a native image using the Native Build Tools, you’ll need a GraalVM distribution on your machine. You can either download it manually on the Liberica Native Image Kit page, or you can use a download manager like SDKMAN!.
Linux and macOS
To install the native image compiler on macOS or Linux, we recommend using SDKMAN!. Get SDKMAN! from sdkman.io and install the Liberica GraalVM distribution by using the following commands:
$ sdk install java 22.3.r17-nik
$ sdk use java 22.3.r17-nik
Verify that the correct version has been configured by checking the output of java -version
:
$ java -version
openjdk version "17.0.5" 2022-10-18 LTS
OpenJDK Runtime Environment GraalVM 22.3.0 (build 17.0.5+8-LTS)
OpenJDK 64-Bit Server VM GraalVM 22.3.0 (build 17.0.5+8-LTS, mixed mode)
Windows
On Windows, follow these instructions to install either GraalVM or Liberica Native Image Kit in version 22.3, the Visual Studio Build Tools and the Windows SDK. Due to the Windows related command-line maximum length, make sure to use x64 Native Tools Command Prompt instead of the regular Windows command line to run Maven or Gradle plugins.
Using Maven
As with the buildpacks support, you need to make sure that you’re using spring-boot-starter-parent
in order to inherit the native
profile and that the org.graalvm.buildtools:native-maven-plugin
plugin is used.
With the native
profile active, you can invoke the native:compile
goal to trigger native-image
compilation:
$ mvn -Pnative native:compile
The native image executable can be found in the target
directory.
Using Gradle
When the Native Build Tools Gradle plugin is applied to your project, the Spring Boot Gradle plugin will automatically trigger the Spring AOT engine.
Task dependencies are automatically configured, so you can just run the standard nativeCompile
task to generate a native image:
$ gradle nativeCompile
The native image executable can be found in the build/native/nativeCompile
directory.
Running the Example
At this point, your application should work. You can now start the application by running it directly:
-
Maven
-
Gradle
$ target/myproject
$ build/native/nativeCompile/myproject
You should see output similar to the following:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.4.0)
....... . . .
....... . . . (log output here)
....... . . .
........ Started MyApplication in 0.08 seconds (process running for 0.095)
The startup time differs from machine to machine, but it should be much faster than a Spring Boot application running on a JVM. |
If you open a web browser to localhost:8080
, you should see the following output:
Hello World!
To gracefully exit the application, press ctrl-c
.