Building Spring Boot Applications with GraalVM: Starter guide
Explore ways to boost app performance and cut resource usage. Compare traditional JVM and GraalVM methods
Hello everyone! In this article, I will show you how you can use 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 use of computational power. In this article, we will develop two versions of the Spring Boot service. The developers will build the first version as a Java Spring Boot service. They will create the second version 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 the non-Graal VM Spring Boot application is 1.066 seconds.
Before proceeding to GraalVM, let’s capture the 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’m using a Mac with ARM architecture, we’ll need a two-step Docker image. The first step will build our project, and the second step will build 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 longer than the non-Graal VM Spring Boot application. On my Mac M1, the time required to build a Docker image is around 6–8 minutes.
Below, we provide the Docker image for the standard service.
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 This starter guide explores building Spring Boot apps. We used both traditional JVM and GraalVM methods. GraalVM native images start up faster and use less memory. However, they take longer to build. 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.