Spring Native: What, Why and How?
Spring Native makes sure we can compile Spring applications to a native executable. To get these native executables Spring Native uses the GraalVM Native Image compiler.
In this article we will go over what is GraalVM Native images, what is Spring Native. We also will go over some advantages and disadvantages of using Spring Native. We will also try to answer the question: ‘When do we use Spring native?’. Last but not least we will create a small application and build a native image from it with the tools that Spring provides us.
What are GraalVM Native Images?
Native Image is a technology to ahead-of-time compile (AOT) 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.
What is Spring Native?
Like stated in the intro of this article, Spring Native is a set of tools and frameworks to make the Spring framework compatible with GraalVM Native Images.
Advantages for native images
Here are some few advantages of native images:
Faster startup
A native image will startup faster, but why can it start faster?
- No class loading: All classes will already we loaded and even partially initiated during build time. This is made possible with the AOT compiler.
- No interpreted code: We don’t have to initialize an interpreter and interpret byte-code
- No JIT: We don’t have to spend any CPU resources to start a JIT compiler or use a JIT compiler
- Generating Image Heap during build: Because we can already partially initiate classes at build time, we can also run some initialization processes at build time. So when we startup we don’t have to execute that part anymore
Lower memory usage
The native image will have less memory usage which makes is more suitable for Docker Images, just to give an example. How does a native image achive this?
- No metadata for loaded classes
- No profiling data for JIT
- No Interpreter code
Building an example Native Image Disadvantages for native images
That all sounds good and way better than normal JVM applications. But there are of course also prices to pay when using native images.
No Java agents, JMX, JVMTI, Java Flight Recorder support
Some of these features are really handy to manage, test and control your JVM applications. Because the native images does not live in a JVM container these features are not available.
Reflection requires extra config
Reflection is widely used in a lot of frameworks so those frameworks need to do extra configuration and work to support native images. That is why Spring created the Spring Native project.
No dumps
You will not be able to able to use thread and heap dumps. There are ways to fetch some information about threads by using Linux Kernel features.
When do we best use Spring Native?
A small disclaimer the next paragraph is my opinion based on the advantages and disadvantages mentioned above!
You best you Spring Native images when you are building CLI tools or create some serverless functions. This all has to do with the short live span of the application and the faster startup time. Spring Cloud Functions fit really good with native images.
Building an example Native Image
We will build a small Spring Application with WebFlux and run this as a Native image. Make sure the following are installed: JDK11, IntelliJ, Maven, Docker.
Generating the project
The important dependency we selected is Spring Native [Experimental]. If you open your pom.xml you can see that is added a Spring Native dependency:
<dependency>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-native</artifactId>
<version>${spring-native.version}</version>
</dependency>
Another thing I should mention that there is a spring-aot-maven-plugin added to the pom.xml:
<plugin>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-aot-maven-plugin</artifactId>
<version>${spring-native.version}</version>
<executions>
<execution>
<id>test-generate</id>
<goals>
<goal>test-generate</goal>
</goals>
</execution>
<execution>
<id>generate</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
This plugin is used to compile your Spring application code to make it ready for native execution. This plugin will also add all the configuration that is needed to handle the Reflection that Spring uses.
Run AOT generate after every build
To make sure that the AOT is generated every time you build the project (To catch issues quickly) we can tell IntelliJ to run the AOT plugin after any build.
Go to the Maven tool window open up plugins then open up spring-aot. After that right click on spring-aot:generate and select Execute after build.
Adding a simple endpoint
Add the following code to the SpringNativeExampleApplication class to create a simple endpoint.
@Bean
public RouterFunction<ServerResponse> routes() {
return route()
.GET("/", request -> ServerResponse.ok().body(Mono.just("Hello everyone!\n"), String.class))
.build();
}
If we now run our application and use curl to get the endpoint we should see the following:
Please stop the application again so we can build our Native Image.
Building the image
Now we need to use maven to build the docker image. Spring boot already has support to build docker images with it’s plugin. You can choose if you execute the maven commands in IntelliJ or your terminal.
$ mvn clean package
$ mvn spring-boot:build-image
The result of the last command will be the following docker image: spring-native-example:0.0.1-SNAPSHOT
$ docker images
The docker images command gives the following:
Now it’s time to start the docker image and trigger our endpoint again to see if we have the same result:
$ docker run --name spring-native-example -p 8080:8080 spring-native-example:0.0.1-SNAPSHOT
The following result:
One thing to note here is that we already see an advantage and that is the fast startup time. The native image for me started in 0.035 seconds, while when I start it from IntelliJ Spring tells me it takes 0.995 seconds.
And if we curl to the application again we see the same result so our endpoint is working as expected:
Conclusion
We went a bit over the theory what native images are and how Spring Native fits into the equation. The example application shows that the native image boots up way faster. Currently Spring Native is still experimental and in beta, but it looks real promising for serverless architectures.