1. Overview
Native images provide various advantages like an instant startup and reduced memory consumption. Therefore, at times, we might want to build a native image of our Java application.
In this tutorial, we'll explore Spring Native to compile and build native images using Buildpacks and GraalVM's native build tools.
2. Basic Setup
As a prerequisite, we'll make sure to install Docker, required later to run native images.
Then, we'll create a simple Spring Boot project named baeldung-spring-native and use it throughout the tutorial to build a native image.
Next, let's add a link to the Spring repo to download dependencies and plugins required in later sections:
<repositories>
<repository>
<id>spring-release</id>
<name>Spring release</name>
<url>https://repo.spring.io/release</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-release</id>
<name>Spring release</name>
<url>https://repo.spring.io/release</url>
</pluginRepository>
</pluginRepositories>
Then, we'll add the latest spring-native Maven dependency:
<dependency>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-native</artifactId>
<version>0.10.0</version>
</dependency>
However, for a Gradle project, Spring Native is automatically added by the Spring AOT plugin.
We should note that each Spring Native version only supports a specific Spring Boot version – for example, Spring Native 0.10.0 supports Spring Boot 2.5.1. So, we should make sure to use the compatible Spring Boot Maven dependencies in our pom.xml.
Next, we'll add a few properties to use Java 8 for compilation:
<properties>
<java.version>1.8</java.version>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
</properties>
Last, we'll create the SpringNativeApp class:
public class SpringNativeApp {
public static void main(String[] args) {
System.out.println("Hello, World! This is a Baledung Spring Native Application");
}
}
3. Buildpacks
Now that our Spring Boot project, baeldung-spring-native, is ready with the basic setup, let's integrate buildpacks in our Spring Boot project to build native images.
3.1. Spring Boot Maven Plugin
First, we'll require the spring-boot-maven-plugin with native image configuration using the Paketo Java buildpacks:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<builder>paketobuildpacks/builder:tiny</builder>
<env>
<BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
</env>
</image>
</configuration>
</plugin>
</plugins>
</build>
Here, we'll use the tiny builder out of the various available builders like base and full to build a native image. Also, we enabled the buildpack by providing the true value to the BP_NATIVE_IMAGE environment variable.
Similarly, when using Gradle, we can add the tiny builder along with the BP_NATIVE_IMAGE environment variable to the build.gradle file:
bootBuildImage {
builder = "paketobuildpacks/builder:tiny"
environment = [
"BP_NATIVE_IMAGE" : "true"
]
}
3.2. Spring AOT Plugin
Next, we'll need to add the Spring AOT plugin that performs ahead-of-time transformations helpful in improving the footprint and compatibility of the native image.
So, let's add the latest spring-aot-maven-plugin Maven dependency to our pom.xml:
<plugin>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-aot-maven-plugin</artifactId>
<version>0.10.0</version>
</plugin>
Similarly, for a Gradle project, we can add the latest org.springframework.experimental.aot dependency in the build.gradle file:
plugins {
id 'org.springframework.experimental.aot' version '0.10.0'
}
Also, as we noted earlier, this will add the Spring Native dependency to the Gradle project automatically.
The Spring AOT plugin provides several options to determine the source generation. For example, options like removeYamlSupport and removeJmxSupport remove the Spring Boot Yaml and Spring Boot JMX support, respectively.
3.3. Build and Run Image
That's it! we're ready to build a native image of our Spring Boot project by using the Maven command:
$ mvn spring-boot:build-image
The Maven command should create a native image of our Spring Boot App with the name baeldung-spring-native:0.0.1-SNAPSHOT.
Note: building native images consumes a lot of resources. So, we must increase the memory and CPU allocated to Docker when we encounter issues while building the native images.
Last, we can run the image of our app on Docker using the docker run command:
$ docker run --rm -p 8080:8080 baeldung-spring-native:0.0.1-SNAPSHOT
So, our app should start almost instantaneously and provide output like:
Hello, World! This is a Baledung Spring Native Application
4. GraalVM Native Build Tools
As an alternative to the Paketo buildpacks, we can use GraalVM‘s native build tools to compile and build native images using GraalVM's native-image compiler.
4.1. Native Image Compiler Installation
As a prerequisite, we must install SDKMAN to smooth the process of setup. Then, we can use the SDKMAN to install GraalVM for Java 8:
$ sdk install java 21.0.0.2.r8
Next, we'll set up JAVA_HOME pointing to GraalVM's 21.0.0.2.r8 distribution.
Last, let's install the native-image compiler provided by the installed GraalVM's 21.0.0.2.r8 distribution:
$ gu install native-image
4.2. Spring AOT
Along with the spring-native dependency, we'll add the latest spring-aot Maven dependency, required for native build tools, in our pom.xml:
<dependency>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-aot</artifactId>
<version>0.10.0</version>
</dependency>
4.3. Spring Boot Maven Plugin
Similar to the Paketo buildpacks, GraalVM's native build tools also require spring-boot-maven-plugin:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
4.4. Spring AOT Maven Plugin
Also, we'll add the spring-aot-maven-plugin to our pom.xml with the generate goal:
<plugin>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-aot-maven-plugin</artifactId>
<version>0.10.0</version>
<executions>
<execution>
<id>generate</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
4.5. Native Profile
Next, we'll add a profile named native with build support of a few plugins like native-maven-plugin and spring-boot-maven-plugin:
<profiles>
<profile>
<id>native</id>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>0.9.0</version>
<executions>
<execution>
<id>build-native</id>
<goals>
<goal>build</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<classifier>exec</classifier>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
This profile will invoke the native-image compiler from the build during the package phase.
However, when using Gradle, we'll add the latest org.graalvm.buildtools.native plugin to the build.gradle file:
plugins {
id 'org.graalvm.buildtools.native' version '0.9.0'
}
4.6. Build and Run
That's it! We're ready to build our native image by providing the native profile in the Maven package command:
$ mvn -Pnative -DskipTests package
The Maven command will create the baeldung-spring-native executor file in the target folder. So, we can run our app by simply accessing the executor file:
$ target/baeldung-spring-native
Hello World!, This is Baledung Spring Native Application
5. Conclusion
In this tutorial, we explored Spring Native along with buildpacks and GraalVM's native build tools.
First, we created a simple Spring Boot project and built a native image using Paketo buildpacks. Then, we examined GraalVM's native build tools to build and run native images by utilizing GraalVM's native-image compiler.
As usual, all the code implementations are available over on GitHub.
The post Introduction to Spring Native first appeared on Baeldung.