Building Spring Boot Applications with GraalVM: Starter guide
Explore how to enhance application performance and reduce resource consumption by comparing traditional JVM and GraalVM approaches
Hello everyone! In this article, I will show you can employ Spring Boot with Graal VM
Introduction
Let’s start with the reason why you need to scale in and scale out your microservices. The reason is the effective utilization of computational power. In this article, we will develop two versions of Spring Boot service. The first version will be the classical Java Spring Boot service, and the second version will be built with GraalVM.
Let’s roll
We will start with basic implementation and add more features later.
Let’s start with building standard Spring Boot, first, we need to declare dependencies in pom.xml or build.gradle
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>me.vrnsky</groupId>
<artifactId>standard</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
At the moment we will create only the starter class and nothing else.
package me.vrnsky;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class StandardAppStarter {
public static void main(String[] args) {
SpringApplication.run(StandardAppStarter.class, args);
}
}
Let’s compile and run our project.
mvn clean package
java -jar target/standard-1.0.0-SNAPSHOT.jar
Observing the following log we can see the startup time of this application.
INFO 3183 --- [ main] me.vrnsky.StandarAppStarter : No active profile set, falling back to 1 default profile: "default"
INFO 3183 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
INFO 3183 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
INFO 3183 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.30]
INFO 3183 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
INFO 3183 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 548 ms
INFO 3183 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 1 endpoint beneath base path '/actuator'
INFO 3183 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/'
INFO 3183 --- [ main] me.vrnsky.StandarAppStarter : Started StandarAppStarter in 1.066 seconds (process running for 1.312)
So the startup time for non-Graal VM Spring Boot application is 1.066 seconds.
Before proceeding to Graal VM let’s capture building project time.
[INFO] The original artifact has been renamed to /Users/vrnsky/IdeaProjects/vrnsky/effective-utilization/standard/target/standard-0.0.1-SNAPSHOT.jar.original
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.040 s
[INFO] Finished at: 2024-10-20T15:51:15+08:00
[INFO] ------------------------------------------------------------------------
Now moving to Graal VM, we will stick with a simple implementation of Spring Boot first — just a starter class. In our pom.xml we have to mention the plugins required for building native artifacts.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>me.vrnsky</groupId>
<artifactId>graal-vm</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
After that let’s build the project.
mvn compile:native -Pnative
The time of building Graal VM application takes a longer time.
Finished generating 'graal-vm' in 1m 35s.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 01:40 min
[INFO] Finished at: 2024-10-20T17:05:07+08:00
[INFO] ------------------------------------------------------------------------
Now we can run our service with the following command.
./target/graal-vm
The app startup time is 0.066 seconds.
INFO 3425 --- [ main] me.vrnsky.GraalVMAppStarter : Started GraalVMAppStarter in 0.066 seconds (process running for 0.086)
Now let’s build a docker image for the Graal VM service. Since I am using Mac with ARM architecture we need to use a two-step docker image — builder for our project and then the second step of building the final image.
FROM --platform=linux/amd64 findepi/graalvm:java17-all as builder
WORKDIR /app
RUN apt-get update && apt-get install -y maven
COPY pom.xml .
COPY src ./src
RUN mvn clean native:compile -Pnative
FROM --platform=linux/amd64 oraclelinux:9-slim
WORKDIR /app
COPY --from=builder /app/target/graal-vm .
EXPOSE 8080
CMD ["./graal-vm"]
The building of a Docker image also takes a longer time than the non-Graal VM Spring Boot application. On my Mac M1 time required to build a docker image is around 6–8 minutes.
The Docker image for standard service is provided below.
FROM eclipse-temurin:17
COPY target/standard-0.0.1-SNAPSHOT.jar /service.jar
CMD java -jar service.jar
EXPOSE 8080
Conlusion
In this starter guide, we explored building Spring Boot applications using both traditional JVM and GraalVM approaches. While GraalVM native images provide significantly faster startup times and smaller runtime footprints, they come with longer build times. This trade-off makes GraalVM particularly suitable for:
Serverless environments where fast startup is crucial
Microservices that need to scale rapidly
Applications with memory constraints
Container-based deployments where image size matters
However, the increased build time might impact development cycles, so teams should carefully consider their specific requirements when choosing between traditional Spring Boot and GraalVM native images.