Running Spring Boot apps as GraalVM Native Images

No Comments

All those Micronaut, Quarkus.io & Co. frameworks sound great! But Spring is the undisputed forerunner in Enterprise Java. Wouldn’t it be great to combine Spring Boot with the benefits of GraalVM?!

asciicast showing extremely fast spring boot startup on console

Spring Boot & GraalVM – blog series

Part 1: Running Spring Boot apps as GraalVM Native Images
Part 2: Running Spring Boot GraalVM Native Images with Docker & Heroku

Spring Boot goes GraalVM

In one of my last year’s projects I came across a situation where I ran out of arguments for using Spring Boot. The idea was to run all microservices on Kubernetes, and Java – more precisely: Spring Boot – was called too slow and fat for that. Back then I really had to swallow this pill and it didn’t feel good.

I’ve been chewing over this topic for a long time! And as I shifted my focus more and more to DevOps topics in the last year, I didn’t have the time to really do something about it. But I never really left the Spring world. As 2020 started, I was ready to come back and find out if there had been any new developments in this area.

And there were! At Spring One Platform 2019 Andy Clement and Sébastien Deleuze gave a great talk about Running Spring Boot Applications as GraalVM Native Images. Both also drive the Spring Experimental project about GraalVM support on GitHub, where you can closely watch every step forward. I guess beloved Starbuxman really pushed Andy and Sébastien to release their work on the Spring Milestones Maven repository, so he could write his introduction on Spring Tips (but I’m sure he will clarify on Twitter 🙂 ). But to be clear for all those who want to start with Spring & GraalVM right away:

Stable GraalVM Native Image support for Spring Boot can be expected with the Spring Framework’s 5.3 release planned in autumn 2020. Best thing is to watch the Spring Roadmap closely.

But this shouldn’t prevent us from getting our hands on this new promising feature! And I really strive to wipe the slate clean for Spring and the Java is too slow and fat for Kubernetes thingy.

GraalVM Native Image & Spring Boot

There has been a lot of buzz about GraalVM lately. The codecentric blog also offers something to read: The introduction to Quarkus.io by Enno Lohmann or an intro to GraalVM actually by Timo Kockert (sorry, German only). So I won’t dig into the secrets about it here too much. But since GraalVM is an umbrella for many projects, we need to focus on a special subproject here: GraalVM Native Image. As we want to reduce the startup times and memory footprint of our Spring Boot Apps, this is project we will take a look at.

GraalVM Native Image can be configured mainly in two ways: either through static configuration via JSON files or through dynamic configuration. Static configuration files can be hand-crafted or generated with the help of the Graal Native Image Agent. Dynamic configuration is able to handle even more complex situations. Here a special Graal Feature interface can be implemented. The classes that implement this interface are then called back throughout the GraalVM Native Image build process.

The startup times and memory footprint of Java applications can be tremendously reduced by shifting the dynamic magic that traditionally happens at runtime to the compilation phase of the Native Image. As this is already a big job when we think about using a little bit of reflection in a normal Java application, this becomes even harder when we look at Spring. Its silver bullet is its biggest drawback at the same time when it comes to native image generation. Although classpath scanning and “magical” automatic configuration made our lives as developers much easier, the GraalVM Native Image build process needs to deal with it.

But the Spring team has really taken up this big challenge! Andy Clement and Sébastien Deleuze already provide an implementation of a Graal @AutomaticFeature for Spring as an experimental project. And there’s already a huge list of example Spring projects using this Feature to create GraalVM Native Images. I found this absolutely fantastic and really wanted to have a closer look!

Installing GraalVM with SDKMAN

Enough talk! Let’s get our hands dirty. As the inclined reader already knows, I always strive to write blog posts that are 100% comprehensible. This one here will hopefully be no exception and therefore you can find an example project on GitHub.

The first thing we need to do in order to use GraalVM is to install it. Thanks to my colleague Christoph Dalski I lately began to really like SKDMAN. You may manage JDKs and Java tools like Maven or GraalVM with it. In order to use SDKMAN, we need to install it locally:

curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"

If SDKMAN has been installed successfully, the command sdk list java should show all possible JDKs that SDKMAN is able to install:

$ sdk list java
 
================================================================================
Available Java Versions
================================================================================
 Vendor        | Use | Version      | Dist    | Status     | Identifier
--------------------------------------------------------------------------------
 AdoptOpenJDK  |     | 14.0.0.j9    | adpt    |            | 14.0.0.j9-adpt
               |     | 14.0.0.hs    | adpt    |            | 14.0.0.hs-adpt
               |     | 13.0.2.j9    | adpt    |            | 13.0.2.j9-adpt
... 
 GraalVM       |     | 20.0.0.r11   | grl     |            | 20.0.0.r11-grl
               |     | 20.0.0.r8    | grl     |            | 20.0.0.r8-grl
               |     | 19.3.1.r11   | grl     |            | 19.3.1.r11-grl
...

The list itself is much longer and you can see the wonderful simplicity of this approach: Don’t ever mess again with JDK installations! Now to install GraalVM based on JDK11, simply run:

sdk install java 20.0.0.r11-grl

SDKMAN now installs GraalVM for us. To have the correct configuration of your PATH variable in place, you may need to restart your console. If everything went well, you should see java -version output the following:

$ java -version
openjdk version "11.0.6" 2020-01-14
OpenJDK Runtime Environment GraalVM CE 20.0.0 (build 11.0.6+9-jvmci-20.0-b02)
OpenJDK 64-Bit Server VM GraalVM CE 20.0.0 (build 11.0.6+9-jvmci-20.0-b02, mixed mode, sharing)

Installing GraalVM Native Image

As initially mentioned, we need GraalVM’s subproject Native Image for our compilations of Spring Boot Apps. Therefore, GraalVM ships with the special tool gu – the GraalVM updater. To list all GraalVM projects that are currently installed, run:

$ gu list
ComponentId              Version             Component name      Origin
--------------------------------------------------------------------------------
graalvm                  20.0.0              GraalVM Core

To install GraalVM Native Image, simply run:

gu install native-image

After that, the native-image command should work for us and is ready for compilation work:

$ native-image --version
GraalVM Version 20.0.0 CE

Creating a simple WebFlux Reactive REST Spring Boot app

In order to create a GraalVM native image from a Spring Boot App, we need at least one. 🙂 And the easiest way is to create it now. So as famous starbuxman suggests, we need to start at Start-Dot-Spring-Dot-IO!

spring initializer at start.spring.io

There we should choose a bleeding-edge Spring Boot Milestone release with 2.3 as a minimum. GraalVM Native Image support for Spring is really in an early stage and is getting better every day. So the docs state:

[Choose a version above] Spring Boot 2.3.0.M1 (you may be able to get some things working with Boot 2.2.X but not 2.1 or earlier)

And in order to build a reactive RESTful Spring Boot App, we need to choose the Spring Reactive Web dependency here. After downloading the skeleton, we continue to create a simple service. In Spring’s reactive manner, we need a handler like HelloHandler.java first:

package io.jonashackt.springbootgraal;
 
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
 
@Component
public class HelloHandler {
 
    protected static String RESPONSE_TEXT= "Hello Reactive People!";
 
    public Mono<ServerResponse> hello(ServerRequest serverRequest) {
        return ServerResponse
                        .ok()
                        .contentType(MediaType.TEXT_PLAIN)
                        .body(BodyInserters.fromValue(RESPONSE_TEXT));
    }
}

We also need a router that will route the HTTP request to our handler. Therefore let’s create a HelloRouter.java:

package io.jonashackt.springbootgraal;
 
import org.springframework.context.annotation.Bean;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.*;
 
@Component
public class HelloRouter {
 
    @Bean
    public RouterFunction<ServerResponse> route(HelloHandler helloHandler) {
        return RouterFunctions.route(
                RequestPredicates.GET("/hello").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)),
                serverRequest -> helloHandler.hello(serverRequest)
        );
    }
}

Now we already have everything in place to create a test case HelloRouterTest.java – using the non-blocking org.springframework.web.reactive.function.client.WebClient of course:

package io.jonashackt.springbootgraal;
 
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
 
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class HelloRouterTest {
 
	@Test void
	should_call_reactive_rest_resource(@Autowired WebTestClient webTestClient) {
		webTestClient.get().uri("/hello")
			.accept(MediaType.TEXT_PLAIN)
			.exchange()
			.expectBody(String.class).isEqualTo(HelloHandler.RESPONSE_TEXT);
	}
}

If you want to create your own Spring Boot app, as always I recommend the great Spring Getting Started Guides!

Finally we build our app with the help of Maven and the command mvn clean package. Then we should be able to run it as usual with java -jar and access it on localhost:8080/hello:

java -jar target/spring-boot-graal-0.0.1-SNAPSHOT.jar

Preparing Spring Boot to be Graal Native Image-friendly

Now in order to be able to natively compile our Spring Boot application, there are some things to prepare before the execution of the native-image command:

1. Relocating Annotation classpath scanning from runtime to build time
2. Disabling usage of GCLIB proxies
3. Detecting autoconfiguration
4. Getting Spring Graal @AutomaticFeature
5. Setting start-class element in pom.xml
6. Preparing configuration variables for native-image command
7. Building the app, expanding the fat JAR & configuring the classpath
8. Crafting the native-image command

1. Relocating annotation classpath scanning from runtime to build time

The first thing we need to handle is the classpath scanning, since this won’t be possible at runtime any more. Already before the whole GraalVM buzz started, there was the project spring-context-indexer, which is an annotation processor that pushes the scan for annotations from runtime to build time:

While classpath scanning is very fast, it is possible to improve the startup performance of large applications by creating a static list of candidates at compilation time. In this mode, all modules that are target of component scan must use this mechanism.

Using the spring-context-indexer in our application would be easy. Simply import it via Maven:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-indexer</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

This would produce a META-INF/spring.components file containing a list of all Spring components, entities and so on that are usually gathered through class path scanning.

But we don’t have to use the spring-context-indexer, since the Graal @AutomaticFeature for Spring does this automatically for us! Additionally, the feature will chase down imported annotated classes like @Import. It “knows” which kinds of annotations lead to reflection needs at runtime, which with GraalVM need to be registered at build time. And as resource files like application.properties also need to be registered at build time, the feature covers those too (remember: the result of the compilation process will only be a native executable).

2. Disabling usage of GCLIB proxies

GraalVM doesn’t support the usage of GCLIB proxies. It comes in handy that starting from Spring Boot 2.2, GCLIB proxies are no longer necessary. It therefore introduces the new proxyBeanMethods option to avoid GCLIB processing. This one is also in use at the example project’s SpringBootHelloApplication.java:

@SpringBootApplication(proxyBeanMethods = false)
public class SpringBootHelloApplication {
    ...
}

As opposed to the GCLIB proxies , the usage of JDK proxies is supported by GraalVM. They just need to be registered at build time. This is also taken care of by the Spring Graal @AutomaticFeature.

3. Detecting autoconfiguration

Spring Boot ships with lots of autoconfiguration projects, which only kick in when there are specific classes found on the class path. Since this is done at runtime, it wouldn’t work with GraalVM. But the Spring Graal @AutomaticFeature also takes care of this. It simply analyzes the META-INF/spring.factories file, where the autoconfiguration classes are usually listed. An example of such a file can be found in the community-driven Spring Boot Starter cxf-spring-boot-starter. So the Spring Graal @AutomaticFeature again pulls the work from runtime to build time – and thus eliminates the need for runtime autoconfiguration.

4. Get Spring Graal @AutomaticFeature

As you already guessed: In order to compile our Spring Boot App as a Native Image, we need to have the latest Spring Graal @AutomaticFeature in place. When I started out to work with GraalVM and Spring in March 2020, there was no Maven Dependency available, since this project is in a very early stage of development. So I initially crafted a script get-spring-feature.sh that cloned and built the project for local usage.

But the Spring guys are moving fast! As there was also a spring.io post released by Starbuxman in April, I think he got Andy Clement and Sébastien Deleuze to release him a Maven dependency available on repo.spring.io/milestone 🙂

So here we go! Now we don’t need to manually download and compile the @AutomaticFeature, we simply add a dependency to our pom.xml:

	<dependencies>
		<dependency>
			<groupId>org.springframework.experimental</groupId>
			<artifactId>spring-graal-native</artifactId>
			<version>0.6.1.RELEASE</version>
		</dependency>
        ...
        <dependencies>
	<repositories>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
		</repository>
	</repositories>
	<pluginRepositories>
		<pluginRepository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
		</pluginRepository>
	</pluginRepositories>

Be sure to also have the Spring Milestones repository definition in place, since the library isn’t available on Maven Central right now!

5. Setting start-class element in pom.xml

In order to be able to execute the Native Image compilation process, we need to provide the command with the full name of our Spring Boot main class.

At first I provided a parameter for my compile.sh script at which we’ll have a look later on. But as the native-image-maven-plugin also relies on this setting, I found it rather okay to provide this class’ name inside our application’s pom.xml:

	<properties>
		...
		<start-class>io.jonashackt.springbootgraal.SpringBootHelloApplication</start-class>
	</properties>

And it’s also nice that we only ever need to set this class once in our pom.xml. We don’t need to bother with this parameter again because we can rely on it automatically in the following steps.

6. Preparing configuration variables for native-image command

I’m pretty sure that the step described here will not be necessary when Spring will officially release the Graal full support in late 2020. And also Starbuxman’s spring.io post points in the direction of having a Maven plugin in place for doing the heavy lifting. But right now in this early stage of development I found it very helpful to have a little more insight into how to execute the native-image command. And it paid off for me – especially in the later stages of this and the following blog posts.

There are great examples of working compile scripts inside the spring-graal-native-samples project. So let’s try to derive our own from that. The full script is available in the example project as well:

#!/usr/bin/env bash
 
echo "[-->] Detect artifactId from pom.xml"
ARTIFACT=$(mvn -q \
-Dexec.executable=echo \
-Dexec.args='${project.artifactId}' \
--non-recursive \
exec:exec);
echo "artifactId is '$ARTIFACT'"
 
echo "[-->] Detect artifact version from pom.xml"
VERSION=$(mvn -q \
  -Dexec.executable=echo \
  -Dexec.args='${project.version}' \
  --non-recursive \
  exec:exec);
echo "artifact version is $VERSION"
 
echo "[-->] Detect Spring Boot Main class ('start-class') from pom.xml"
MAINCLASS=$(mvn -q \
-Dexec.executable=echo \
-Dexec.args='${start-class}' \
--non-recursive \
exec:exec);
echo "Spring Boot Main class ('start-class') is 'MAINCLASS'"

The first part of the script is dedicated to define required variables for the GraalVM Native Image compilation. The variables ARTIFACT, VERSION and MAINCLASS can be simply derived from our pom.xml with the help of the Maven exec plugin.

7. Building the app, expanding the fat JAR & configuring the classpath

In the next section of the compile.sh script, we clean (aka remove) the target directory and build our Spring Boot app via the well-known mvn package command:

echo "[-->] Cleaning target directory & creating new one"
rm -rf target
mkdir -p target/native-image
 
echo "[-->] Build Spring Boot App with mvn package"
mvn -DskipTests package

After the build, the Spring Boot fat JAR needs to be expanded and the classpath needs to be set to the content of the results. Also the Spring Graal @AutomaticFeature needs to be available on the classpath. Therefore we need the correct path to the spring-graal-native-0.6.1.RELEASE.jar file inside our compile.sh script:

echo "[-->] Expanding the Spring Boot fat jar"
JAR="$ARTIFACT-$VERSION.jar"
cd target/native-image
jar -xvf ../$JAR >/dev/null 2>&1
cp -R META-INF BOOT-INF/classes
 
echo "[-->] Set the classpath to the contents of the fat JAR & add the Spring Graal AutomaticFeature to the classpath"
LIBPATH=`find BOOT-INF/lib | tr '\n' ':'`
MAVEN_REPO_HOME=~/.m2/repository
FEATURE=$MAVEN_REPO_HOME/org/springframework/experimental/spring-graal-native/0.6.1.RELEASE/spring-graal-native-0.6.1.RELEASE.jar
CP=BOOT-INF/classes:$LIBPATH:$FEATURE

The script assumes that your Maven repository resides in your user home under ~/.m2/repository. You need to alter the variable MAVEN_REPO_HOME inside compile.sh if that isn’t the case!

8. Crafting the native-image command

Now finally the GraalVM Native Image compilation is triggered with lots of appropriate configuration options. If you need inspiration on how to configure the native-image command suitable for your Spring Boot application, I would advise you to look into the spring-graal-native-samples project. Those parameters need to be adapted to different types of Spring applications right now and look quite different depending on whether you use a Tomcat-based application including Spring Data REST or a Netty-based reactive app like in this example. It could be necessary, for example, that concrete classes are defined with --initialize-at-build-time=class.name.here. You can also go the hard way and walk through all the exceptions the native-image command throws out. Sometimes there’s no other way than that. I’am also pretty sure this will change with Spring’s late 2020 releases.

A working native-image command for our Netty-based reactive app looks like this:

time native-image \
  --no-server \
  --no-fallback \
  --initialize-at-build-time \
  -H:+TraceClassInitialization \
  -H:Name=$ARTIFACT \
  -H:+ReportExceptionStackTraces \
  -Dspring.graal.remove-unused-autoconfig=true \
  -Dspring.graal.remove-yaml-support=true \
  -cp $CP $MAINCLASS;

Some parameters can be relied on for most Spring Boot applications for now. Especially the --no-server flag should be used to be sure the compile process produces reproducible results (there’s an open issue right now in GraalVM). Also, it is good to know that the Spring Graal @AutomaticFeature takes care of the two default options when it comes to Spring compilation: --allow-incomplete-classpath and --report-unsupported-elements-at-runtime. We don’t explicitely need to define them if we use the @AutomaticFeature.

The other options need to be defined explicitly: --no-fallback disables the fallback on a regular JVM and enforces a native image only runtime. Currently --initialize-at-build-time is only needed for Netty-based applications. It initializes all classes at build time by default, but can trigger compatibility issues with other applications. Both the parameters -H:+TraceClassInitialization and -H:+ReportExceptionStackTraces will aid in debugging if something goes wrong.

All those parameters preceded by -Dspring.graal. are Spring Graal feature-specific configuration options. We use -Dspring.graal.remove-unused-autoconfig=true and -Dspring.graal.remove-yaml-support=true here to enable faster compilation and smaller executables.

Finally the other parameters like -H:Name=$ARTIFACT and -cp $CP $MAINCLASS are needed to specify the executable’s name and the correct classpath for the native image compilation to work. The docs also provide a list of all configuration parameters the native-image command might need.

In severe cases it could be necessary to use the Spring Graal @AutomaticFeature together with the initially mentioned GraalVM agent. The docs cover how to do this “hybrid” execution.

Running the native image compilation

Now we are where we wanted to be in the first place. We have everything in place to run native image compilation. Simply execute:

./compile.sh

The compile step does take its time (depending on your hardware!). On my MacBook Pro 2017 this takes around 3 to 4 minutes. As we use the --no-server option, you can also guess the amount of RAM my machine has, since this option also tells the native image compilation to grab around 80% of the system’s memory. I prepared a small asciinema record so that you can see how the compilation process works:

asciicast showing GraalVM Native Image compilation of Spring Boot application

If your console shows something like the following:

[spring-boot-graal:93927]   (typeflow):  74,606.04 ms, 12.76 GB
[spring-boot-graal:93927]    (objects):  58,480.01 ms, 12.76 GB
[spring-boot-graal:93927]   (features):   8,413.90 ms, 12.76 GB
[spring-boot-graal:93927]     analysis: 147,776.93 ms, 12.76 GB
[spring-boot-graal:93927]     (clinit):   1,578.42 ms, 12.76 GB
[spring-boot-graal:93927]     universe:   4,909.40 ms, 12.76 GB
[spring-boot-graal:93927]      (parse):   6,885.61 ms, 12.78 GB
[spring-boot-graal:93927]     (inline):   6,594.06 ms, 12.78 GB
[spring-boot-graal:93927]    (compile):  33,040.00 ms, 12.79 GB
[spring-boot-graal:93927]      compile:  50,001.85 ms, 12.79 GB
[spring-boot-graal:93927]        image:   8,963.82 ms, 12.79 GB
[spring-boot-graal:93927]        write:   2,414.18 ms, 12.79 GB
[spring-boot-graal:93927]      [total]: 232,479.88 ms, 12.79 GB
 
real	3m54.635s
user	16m16.765s
sys	1m55.756s

you’re now be able to fire up your first GraalVM native app!. How cool is that?!! All you have to do is run the generated executable /target/native-image/spring-graal-vm:

$ ./target/native-image/spring-graal-vm
 
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::
 
2020-05-01 10:25:31.200  INFO 42231 --- [           main] i.j.s.SpringBootHelloApplication         : Starting SpringBootHelloApplication on PikeBook.fritz.box with PID 42231 (/Users/jonashecht/dev/spring-boot/spring-boot-graalvm/target/native-image/spring-boot-graal started by jonashecht in /Users/jonashecht/dev/spring-boot/spring-boot-graalvm/target/native-image)
2020-05-01 10:25:31.200  INFO 42231 --- [           main] i.j.s.SpringBootHelloApplication         : No active profile set, falling back to default profiles: default
2020-05-01 10:25:31.241  WARN 42231 --- [           main] io.netty.channel.DefaultChannelId        : Failed to find the current process ID from ''; using a random value: 635087100
2020-05-01 10:25:31.245  INFO 42231 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port(s): 8080
2020-05-01 10:25:31.245  INFO 42231 --- [           main] i.j.s.SpringBootHelloApplication         : Started SpringBootHelloApplication in 0.078 seconds (JVM running for 0.08)

Our Spring Boot App started in 0.078 seconds!! Simply access the App via localhost:8080/hello.

Comparing startup time & memory footprint

Ok, the initial goal was to run our beloved Spring Boot apps at lightning speed and to clear out the “argument” that Java is too slow and fat for cloud-native deployments. Therefore let’s take a look at our “normal” Spring Boot app that we’re able to run with:

$ java -jar target/spring-boot-graal-0.0.1-SNAPSHOT.jar
 
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::             (v2.3.0.M4)
 
2020-04-30 15:40:21.187  INFO 40149 --- [           main] i.j.s.SpringBootHelloApplication         : Starting SpringBootHelloApplication v0.0.1-SNAPSHOT on PikeBook.fritz.box with PID 40149 (/Users/jonashecht/dev/spring-boot/spring-boot-graalvm/target/spring-boot-graal-0.0.1-SNAPSHOT.jar started by jonashecht in /Users/jonashecht/dev/spring-boot/spring-boot-graalvm)
2020-04-30 15:40:21.190  INFO 40149 --- [           main] i.j.s.SpringBootHelloApplication         : No active profile set, falling back to default profiles: default
2020-04-30 15:40:22.280  INFO 40149 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port(s): 8080
2020-04-30 15:40:22.288  INFO 40149 --- [           main] i.j.s.SpringBootHelloApplication         : Started SpringBootHelloApplication in 1.47 seconds (JVM running for 1.924)

The standard way takes about 1.47 seconds to start up and it uses around 491 MB of RAM, which outlines a simple top command for us:

PID    COMMAND      %CPU TIME     #TH  #WQ  #POR MEM  PURG CMPR PGRP  PPID STATE    BOOSTS    %CPU_ME %CPU_OTHRS UID  FAULTS  COW  MSGS MSGR SYSBSD SYSM CSW    PAGE IDLE POWE
40862  java         0.1  00:05.46 27   1    112  491M 0B   0B   40862 1592 sleeping *0[1]     0.00000 0.00000    501  136365  1942 5891 2919 52253+ 8577 21848+ 7148 733+ 0.8

Now in comparison to that, with our natively compiled Spring Boot app, we already saw a startup time of only 78 milliseconds. Additionally, our application only consumes 30 MB of RAM:

PID    COMMAND      %CPU TIME     #TH  #WQ  #POR MEM  PURG CMPR PGRP  PPID STATE    BOOSTS    %CPU_ME %CPU_OTHRS UID  FAULT COW  MSGS MSGR SYSB SYSM CSW  PAGE IDLE POWE INST CYCL
42231  spring-boot- 0.0  00:00.08 7    1    38   30M  0B   0B   42231 1592 sleeping *0[1]     0.00000 0.00000    501  17416 2360 77   20   2186 186  174  27   2    0.0  0    0

So with a default Spring App we have around 500 MB memory consumption, a natively compiled Spring app has only 30 MB. This means we can run more than 15 Spring microservices with the same amount of RAM we needed for only one standard Spring microservice! Woohoo! 🙂 And not to mention the startup times. Around 1.5 seconds versus only 78 milliseconds. So even our Kubernetes cluster should be able to scale our Spring Boot Apps at lightning speed!

Boot at lightning speed – Spring Boot & GraalVM

I am absolutely stunned at how successful the marriage between Spring Boot and natively compiled GraalVM images already is. Thanks to the fantastic work of the Spring team and the Spring Graal @AutomaticFeature project, we’re already able to see what’s coming soon. And in 2020 I will no longer accept anyone telling me that Java/Spring is too slow and fat for real cloud-native deployments! Of course there’s still a long way to go and production deployments should wait until autumn 2020, when Spring officially releases full GraalVM native image support. But there’s no excuse not to start today and check out these great features.

As always, I have some topics left that would be beyond the scope of this article: What about doing GraalVM Native Image compiliations on Cloud CI systems? And is it even possible – even though full memory access is a must – to do all that inside Docker containers? And if all that works: How could we deploy a Dockerized and Nativized (what a word!) Spring Boot app into some cloud PaaS? Many exciting topics remain to be looked into. So stay tuned for follow-ups!

Jonas Hecht

Trying to bridge the gap between software architecture and hands on coding, Jonas hired at codecentric. He has deep knowledge in all kinds of enterprise software development, paired with passion for new technology. Connecting systems via integration frameworks Jonas learned to not only get the hang of technical challenges.

Comment

Your email address will not be published. Required fields are marked *