Spring Native and Serverless with Spring Boot apps on Google Cloud!

Abirami Sukumaran
Google Cloud - Community
7 min readJul 16, 2023
GraalVM Logo

This blog is part 3 of the 5-part Spring Boot on Google Cloud series. Read Part 1 and Part 2 if you missed it previously.

Before we dive into what is Spring Native and how to deliver that, we will understand the basic concepts quickly.

JIT (Just In Time) Compilation

We all know that Java supports its signature capability — being portable (WORA) — (“Write once, Run Anywhere”) by compiling the .java source files into .class files, in bytecode at compile time, which then is interpreted by the JVM (Java Virtual Machine) on any machine that we wish to run the code. To avoid the cost involved in interpreting bytecode, JVM compiles the frequently invoked functions / code into native code at run-time which is what we fondly refer to as the JIT (Just In Time) Compilation.

Why am I telling you this?

‘Cos, as opposed to JIT, Spring Native supports an approach called AOT (Ahead of Time) Compilation. It means, all reachable code is converted into a native executable at compile-time itself. This executable contains application classes, dependencies, libraries and native code.

Hmm, is it better than JIT?

It’s a trade-off. With JIT, you get portability but with AOT, we trade that for memory efficiency, reduced application startup time. Also you have to remember that, since code is converted into native executable at compile-time, you have to ensure all dependencies like loading a file, are made available at build time.

So, where would I use this?

Applications that require instant startup, applications that are running in highly memory constrained environment, applications that require faster and efficient deployment by packaging into lightweight containers.

Ok, get back to Spring Native

Spring Native is that which enables the process of converting Spring applications into native executables using some open sourced utilities. It is interesting and important to note that this process, at compile-time, grabs all your reachable code (functions) and converts them into native images, and hence is memory-intensive and also lengthy.

… And what is that Open-Sourced utility Spring Native employs?

GraalVM (General Recursive Applicative and Algorithmic Language Virtual Machine) is a high performing open-sourced JDK distribution written for Java (and JVM languages), Ruby, JavaScript, Python etc. What it does for Spring Native is that, it provides a Native Image Builder to build native code from your Java applications, package it with the VM into a native executable!

Alright, get to business

Spring Native is fully integrated into Spring Boot 3.0. However in this hands-on blog, you will learn how to achieve Spring Native in Spring Boot 2 application and deploy it on Cloud Run (Google Cloud’s Serverless Deployment Service) in only 5 steps!!!

We are using Spring Boot 2.7.1, Spring-Native Dependency 0.12.1 (experimental) and Maven build. Since it is experimental version, the difference here is we will include the artifacts manually in pom.xml as the experimental artifacts were not made available in Maven’s central repo.

I hope you have already familiarized yourself with Google Cloud console in part 1 of the series, if not quickly read that short one up, so you are aware of how to launch Google Cloud Shell Terminal and execute the rest of the steps in this blog.

Step 1 — Bootstrap your Spring Boot application

Run the below command in Cloud Shell Terminal:

curl https://start.spring.io/starter.tgz -d dependencies=web -d baseDir=ntv-boot-app -d javaVersion=11 -d bootVersion=2.7.1 -d type=maven-project | tar -xzvf -

This will create a project ntv-boot-app in your Cloud Shell machine with the dependencies in pom.xml and source in com / example / demo / DemoApplication java file.

Add the following code in a new file index.html in the path ntv-boot-app/src/main/resources/static/

<html>
<body>
WELCOME to Spring Native Implementation!
</body>
</html>

Compile the code and run the application by executing the following commands in the Cloud Shell terminal:

./mvnw package

mvn spring-boot:run

Go to Web Preview on the top right corner of your Cloud Shell terminal to view the result like this:

Image showcasing the index.html page of the Spring Boot application running locally

Step 2 — Build a regular image for this application

To build an image for this application, run the following command in Cloud Shell terminal

mvn spring-boot:build-image

This creates the docker image and you can see the steps involved in it in your terminal, like creating a jar, using Paketo builder to pull the necessary libraries and frameworks and finally the built docker image and the time it took. In this case, execute the command below to view the details

docker images demo

As you can notice below, an image of size 262 MB is created in the demo repository.

****@cloudshell:~/ntv-boot-app (***)$ docker images demo

REPOSITORY TAG IMAGE ID SIZE
demo 0.0.1-SNAPSHOT e94231ea6a7f 262MB

Now let’s compare this regular Spring Boot image with its Spring-Native counterpart.

Step 3— Make this Spring Boot App Spring-Native

To make this a Spring Native app, start by including the following dependency in the pom.xml

<dependency>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-native</artifactId>
<version>0.12.1</version>
</dependency>

Include the repositories for the experimental artifacts in the pom.xml file

 <repositories>
<repository>
<id>spring-release</id>
<name>Spring release</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>

<pluginRepositories>
<pluginRepository>
<id>spring-release</id>
<name>Spring release</name>
<url>https://repo.spring.io/milestone</url>
</pluginRepository>
</pluginRepositories>

Next, a) update the Spring Boot Maven Plugin to use the Paketo build pack with Native Image configuration and b) Add the AOT (Ahead Of Time) Maven Plugin to pom.xml. Make sure your pom.xml’s <build> segment looks like this:

<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>

<plugin>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-aot-maven-plugin</artifactId>
<version>0.12.1</version>
<executions>
<execution>
<id>generate</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

Step 4 — Build the Spring-Native Image

Run the following command from the Cloud Shell terminal:

mvn spring-boot:build-image

You will notice that the build took more time than regular build and took up extra memory at compile time. But on the bright side, you will see that the size of the native image is far less compared to the regular image. You can check this by running the docker images demo again on Cloud Shell:

docker images demo

As you can notice below, an image of size 86.1MB is created in the demo repository.

****@cloudshell:~/ntv-boot-app (***)$ docker images demo

REPOSITORY TAG IMAGE ID SIZE
demo 0.0.1-SNAPSHOT 0a9227545d6b 86.1MB

From 262MB to 86.1MB is a huge reduction.

Step 5— Deploy the Spring-Native App in Cloud Run

To deploy the spring native image to Cloud Run, we need to 1) Create a repository in Artifact Registry 2) Tag the spring native image 3) Push it into the AR repository 4) Deploy in Cloud Run

In Google Cloud Console, search for Artifact Registry and when it opens the Artifact Registry console, click Create Repository button as highlighted in the following image:

Image highlighting Artifact Registry console and CREATE REPOSITORY button

Select the repository name, format, location as “spring-native-repo”, “Docker” and “us-central1” respectively as seen below:

Image showing name, format and mode of the new repository being created in Artifact Registry

Click CREATE and the repository is created. On the console you will see the repositories in your Artifact Registry. Select the one we just created and on the right pane under Permissions tab, click ADD PRINCIPAL to make sure you’re authenticated to push your image into this repository:

Image showing Artifact Registry Repository list with the new repo selected and ADD PRINCIPAL highlighted

Click ADD PRINCIPAL and enter your user in the New Principals and in Assign Roles select Artifcat Registry and Artifact Registry Writer:

Image showing Grant Access dialog box for the new repo for the role of Artifact Registry Writer

The next step is to tag and push our native image to the repository. In all the commands below, replace <<YOUR_PROJECT_ID>> with your current working project id.

Tag:

docker tag  demo:0.0.1-SNAPSHOT \
us-central1-docker.pkg.dev/<<YOUR_PROJECT_ID>>/spring-native-repo/sn-image:first

Push:

docker push us-central1-docker.pkg.dev/<<YOUR_PROJECT_ID>>/spring-native-repo/sn-image:first

Finally, to deploy the app in Cloud Run, execute the below command from Cloud Shell terminal:

gcloud run deploy spring-native-app --image us-central1-docker.pkg.dev/<<YOUR_PROJECT_ID>>/spring-native-repo/sn-image:first --platform managed --region us-central1 --allow-unauthenticated

Once deployed, you should the service endpoint in your terminal:

Image showing Cloud Shell terminal with the deployed Cloud Run Service URL

Once you click the deployed app URL, you would see that the native-image application started up really quickly compared to the regular image!

Image of the Cloud Run deployed application

Conclusion

I hope this blog was helpful in explaining the concepts of Spring Native, Ahead Of Time compilation and usage of GraalVM in building native images that help achieve faster startup time, smaller images and efficient deployments.

This blog is part of my Spring Boot on Google Cloud series. Read Part 1 and Part 2 if you missed it previously!

--

--

Abirami Sukumaran
Google Cloud - Community

Developer Advocate Google. With 18 years in data and software dev leadership, I’m passionate about addressing real world opportunities with technology.