Spring Native: What, Why and How
Spring Native: What, Why and How
Introduction
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.
Compared to the Java Virtual Machine, native images can enable cheaper and more sustainable hosting for many types of workloads. These include microservices, function workloads, well suited to containers, and Kubernetes.
Using native image provides key advantages, such as instant startup, instant peak performance, and reduced memory consumption.
There are also some drawbacks and trade-offs that the GraalVM native project expect to improve on over time. Building a native image is a heavy process that is slower than a regular application. A native image has fewer runtime optimizations after warmup. Finally, it is less mature than the JVM with some different behaviors.
The key differences between a regular JVM and this native image platform are:
- A static analysis of your application from the main entry point is performed at build time.
- The unused parts are removed at build time.
- Configuration is required for reflection, resources, and dynamic proxies.
- Classpath is fixed at build time.
- No class lazy loading: everything shipped in the executables will be loaded in memory on startup.
- Some code will run at build time.
- There are some limitations around some aspects of Java applications that are not fully supported.
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 archive this?
- No metadata for loaded classes
- No profiling data for JIT
- No Interpreter code
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.
Limited Logging
- Logback is supported, but not configuration with
logback.xml
so please configure it withapplication.properties
orapplication.yml
for now, see Add support for logback.xml configuration file for more details. - Log4j2 is not supported yet, see Add support for Log4j2.
When do we best to use Spring Native
Spring Native images is the best suited 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
The easiest way to start with Spring Native is probably to go to Spring Initializer Site, add the dependencies Spring Reactive Web and Spring Native [Experimental], and read the reference documentation. Make sure to configure properly the Spring AOT Maven and Gradle plugins that are mandatory to get proper native support for your Spring application.
Make sure the following are installed: Java 11, Maven/Gradle, Docker, and GraalVM native-image compiler.
Update pom.xml
Add Spring Native [Experimental] dependency to the pom.xml:
1 | <dependency> |
Add spring-aot-maven-plugin to the pom.xml:
1 | <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.
Spring AOT
Spring AOT (Ahead-of-Time) aims at improving compatibility and footprint of native images for Spring Native applications. To achieve that, this projects ships Maven and Gradle build plugins that generate and compile a separate set of Java sources to be packaged with your Spring Boot application. By looking at your application classpath and configuration, Spring AOT brings some of the configuration processing at build time and streamlines the native image compilation process.
Maven goals spring-aot:generate (process-test-classes phase) and spring-aot:test-generate (prepare-package phase) are automatically invoked in the Maven lifecycle when using the mvn verify
or mvn package
commands.
1 | $ mvn -D skipTests=true package spring-aot:generate |
Add endpoint
1 |
|
Verification Endpoint
1 | # mvn spring-boot:run |
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.
1 | $ java -version |
1 | $ mvn -U -D skipTests=true package spring-boot:build-image |
Verification Native Image
1 | $ curl -iSS http://localhost:8080 |
1 | $ curl -iSS --http2 http://localhost:8080 |
1 | $ curl -iSS --http2-prior-knowledge http://localhost:8080 |
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.