Developing Your First GraalVM Native Application

Now that we have a good overview of GraalVM Native Images and how the Spring ahead-of-time engine works, we can look at how to create an application.

There are two main ways to build a Spring Boot native image application:

  • Using Spring Boot support for Cloud Native Buildpacks 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 includes buildpack support for native images directly for both Maven and Gradle. 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 builder used for the images is paketobuildpacks/builder-jammy-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.3.0-SNAPSHOT</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 buildpack 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.3.0-SNAPSHOT)
....... . . .
....... . . . (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.