Spring Boot 3 finally integrated the Spring Native project into the core framework, giving you the option to compile your application to a native executable. The Spring team also worked hard on making most available Spring-Boot starters compatible with the new native compile option. This means that you can now compile your application natively without major code changes if you stick to Spring-Boot modules in your application.
Pros and Cons of Native Applications
So what are the benefits of a native application compared to a regular JVM application?
- Native Java applications are a lot smaller than regular JVM based application. By the native compile all unused classes and methods from core Java, your own code and dependencies will be removed from the final build artifact. This usually means, that your application is only 30% the size of a regular Java application.
- Native applications have a lot faster startup times than JVM applications. If you start an application on a JVM, all classes first need to be loaded into the JVM and sometimes compiled to native code at runtime. All this is done at compile time for a native application. Startup times for native applications are usually within milliseconds compared to seconds needed to start a regular JVM application.
- The memory footprint of a native application is smaller, since there is no JVM running in the background. Only code that is actually used by your application will be loaded into memory.
Of course native applications don’t only come with benefits, but also have some drawbacks that you should consider:
- The native compile process takes quite a long time compared to a regular Java compilation. This is because the native compiler needs to analyze what classes and methods are used in the code. This is quite a task since this is not only done for your code but also for all core Java libraries and third-party dependencies that you included in your project. A native compile can easily take a couple of minutes. That is definitely not great for local development.
- Out of the box you cannot use reflection to dynamically load classes in your application. Since the compiler removes all unused classes, you cannot load new classes during runtime even if they should normally be on the classpath. This was one of the major roadblocks of the native compilation for most Spring modules, since the Spring Framework uses a lot of reflection. However, you can now specify that the native compiler should include a class in your application although it is not obviously used in the code. The Spring team did this for most Spring-Boot modules, which was the bulk of work to that had to be done to make Spring applications compatible to the native compiler.
- The application will only run on the operating system and processor architecture you compiled it on. If you e.g. compiled on Linux and an ARM processor, you won’t be able to run it on Windows machine or a mobile phone. However, if you are going to run you application on Docker or Kubernetes in a cloud environment, this is usually not a big deal, since most cloud vendors run your containers on Linux based systems anyways.
But that’s enough theory for now. Let’s have a look how a native application is build with Spring-Boot 3 by a hands-on example.
Prerequisites
First, you will need to install GraalVM on your development machine. GraalVM is alternative JVM that will help you to compile your application natively.
After you installed GraalVM make sure that it is actually used by checking the Java version:
java -versionThis should output something like this:
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, sharing)I used GraalVM 22.3 which supports Java SE 17. To run Spring-Boot 3 you will need at least Java version 17 or higher.
You will also need to have Maven or Gradle installed on your machine. Since this is also required to develop regular Spring applications, you should have installed either anyways. I will use Maven for the rest of this demo, but you can use GraalVM with Gradle too.
The Spring-Boot Application
Let’s go to Spring Initializr and create a simple Spring-Boot 3 application to later compile it to a native executable. I used the following parameters to create my project:
- Spring Boot version 3.1.1
- Maven as the build tool
- Java 17 as the Java version
Furthermore, we will need two Spring modules in our project:

We need the GraalVM Native Support module to compile our application with GraalVM. This module will change the default Spring-Boot-Plugin by a GraalVM-Plugin in your pom file. We also need the Spring Web module to create a simple Rest controller to return some demo data.
With these parameters selected, generate your project, download the generated zip file and unzip it into a folder of your choice.
To get some executable code, let’s create a simple http endpoint that returns a couple of Book objects in JSON format:
package com.jot.nativeimage.endpoints;
import java.util.List;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class BookEndpoint {
@GetMapping
@RequestMapping("/books")
public List<Book> getBooks(){
return List.of(new Book("The Lord of The Rings","J.R.R. Tolkien"),
new Book("The Lord of Flies","William Golding"),
new Book("1984","George Orwell"));
}
}
record Book (String name, String author){}If we start this application and hit it with a GET request on localhost:8080, we get a couple of books in JSON format returned:
[
{
"name": "The Lord of The Rings",
"author": "J.R.R. Tolkien"
},
{
"name": "The Lord of Flies",
"author": "William Golding"
},
{
"name": "1984",
"author": "George Orwell"
}
]How to Create a native Application
With all the code in place, we can now create our native application by the following Maven command executed from the root folder of our project:
mvn -Pnative native:compileThe -Pnative flag will enable a pre-configured native Maven profile that is inherited by the Spring-Boot 3 Maven parent. The native:compile goal will start a GraalVM native compilation by the GraalVM Spring-Boot plugin .
On my machine this build runs for about 3 minutes and generates a lot of output in the console. After the short wait, the executable is ready for action in the /target folder:
Produced artifacts:
/workspace/spring-boot-3-native-images/target/native-image-demo (executable)
/workspace/spring-boot-3-native-images/target/native-image-demo.build_artifacts.txt (txt)
========================================================================================================================
Finished generating 'native-image-demo' in 3m 10s.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 03:24 min
[INFO] Finished at: 2023-06-28T17:16:08Z
[INFO] ------------------------------------------------------------------------You can now start the native executable by:
./target/native-image-demoThis starts the application on my machine in around 60 milliseconds :

This executable is a standalone application. You won’t even need a Java runtime environment to start it, since all required Java SE classes are included in the file.
If you want to test this yourself, you can use this gitpod link and start the repo in your browser.
On short note about executing a native compilation on a Windows machine. Since GraalVM uses some OS specific compile tools to generate the native executable, you will need to install these tools on your Windows machine first to start a build. The easiest way to do so is to install Microsoft Visual Studio, which comes with a C++ compiler and several other build tools out of the box.
Linus and Mac operation systems have all required compile tools preinstalled with the OS, so there is no additional installation required.
Create a Native Docker image
Having a native binary of your application is nice, but in most cases you also want a Docker image from that file to deploy your application to a cloud platform like AWS or Azure.
You could now build your own Docker image by providing your own Dockerfile and run a Docker build, but Spring-Boot 3 comes with a very handy feature to do all this for you.
Spring-Boot 3 supports the execution of a native Paketo Buildpack. I don’t want to go into the details of buildpacks here, but in a nutshell a buildpack gives you a standardized way to build and package your application. In our use case, we want to do a native build and package the result as a Docker image.
You can do all this with Spring-Boot 3 out of the box by simply using the following Maven command:
mvn -Pnative spring-boot:build-imageThis will instruct the Spring-Boot plugin to compile your application to a native executable and put it into a Docker image. The only prerequisite for this is a running Docker daemon on your machine. This also works on Windows, if you have Docker desktop installed on your machine. Behind the scenes the buildpack will start a Docker image that runs the compilation and the Docker build for the finished binary.
After the build is finished you can start the application by the following Docker command:
docker run --rm -p 8080:8080 docker.io/library/native-image-demo:0.0.1-SNAPSHOTThis will start the application and you can access it on localhost:8080 to get our list of books.
This images starts in just 0.282 seconds on my machine:

Looking at the image size, the packaged application is only around 100 mb. If you compare this to an image for a regular Java application including the JVM, which needs around 300mb, the native image is only one third in size.
Conclusion
There is very little reason not to use this cool new Spring-Boot feature if you are running your applications in a public or inhouse cloud. Simply check if all your third-party dependencies are compatible to Spring-Boot 3 already. If not, there are ways to tell the GraalVM compiler to include classes that are loaded dynamically, but this is a topic for another post.
For local development, the compile times are just too long but for your CI/CD pipeline this won’t matter much and will safe you a lot of disk space and memory for your cloud applications in production.
If you want to check out the code for this demo, you can find it in here on Github.
Schreibe einen Kommentar