DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Comparing the Efficiency of a Spring Boot Project to a Go Project
  • Providing Enum Consistency Between Application and Data
  • Java’s Next Act: Native Speed for a Cloud-Native World
  • How to Build a New API Quickly Using Spring Boot and Maven

Trending

  • Analyzing “java.lang.OutOfMemoryError: Failed to create a thread” Error
  • SQL Server Index Optimization Strategies: Best Practices with Ola Hallengren’s Scripts
  • Key Considerations in Cross-Model Migration
  • Beyond Microservices: The Emerging Post-Monolith Architecture for 2025
  1. DZone
  2. Coding
  3. Java
  4. How To Build a Spring Boot GraalVM Image

How To Build a Spring Boot GraalVM Image

In this article, readers will use a tutorial to learn how to build a Spring Boot GraalVM images and using Reflection, including guide code block examples.

By 
Gunter Rotsaert user avatar
Gunter Rotsaert
DZone Core CORE ·
Mar. 21, 23 · Tutorial
Likes (1)
Comment
Save
Tweet
Share
5.8K Views

Join the DZone community and get the full member experience.

Join For Free

In this article, you will learn how to build a GraalVM image for your Spring Boot application. Following these practical steps, you will be able to apply them to your own Spring Boot application. Enjoy!

Introduction

Java is a great programming language and is platform independent. Write once, run anywhere! But this comes at a cost. Java is portable because Java compiles your code to bytecode. Bytecode is computer object code, which an interpreter, (read: Virtual Machine), can interpret and convert to machine code. When you start your Java application, the Virtual Machine will convert the bytecode into bytecode specific for the platform, called native machine code. This is done by the just-in-time compiler (JIT). As you will understand, this conversion takes some time during startup.

Assume you have a use case where fast startup time is very important. An example is an AWS Lambda written in Java. AWS Lambda’s are not running when there is no application activity. When a request needs the AWS Lambda to run, the Lambda needs to start up very fast, execute, and then shutdown again. Every time the Lambda starts, the JIT compiler needs to do its work. In this use case, the JIT compilation takes up unnecessary time because you already know which platform you are running. This is where ahead-of-time compilation (AOT) can help. With AOT, you can create an executable or “native image” for your target platform. You do not need a JVM anymore and no JIT compilation. This results in a faster startup time, lower memory footprint and a lower CPU usage.

GraalVM can compile your Java applicaton into a native image. Spring Boot had an experimental project called Spring Native, which helps Spring Boot developers create native images. As from Spring Boot 3, Spring Native is part of Spring Boot and out of the experimentation phase.

In the remainder of this article, you will create a basic Spring Boot application and create a GraalVM image for it.

If you want to learn more about GraalVM in an interactive way, the GraalVM workshop is strongly recommended.

The sources used in this article are available at GitHub.

Prerequisites

Prerequisites for this article are:

  • Ubuntu 22.04 
  • Basic Linux knowledge.
  • Basic Java and Spring Boot knowledge.
  • SDKMAN is used for switching between JDKs.

Sample Application

First thing to do is create a sample application. Browse to the Spring Initializr and add dependencies, Spring Web and GraalVM Native Support. Make sure you use Spring Boot 3, generate the project, and open it in your favourite IDE.

Add a HelloController with one endpoint returning a hello message:

Java
 
@RestController
public class HelloController {
 
    @RequestMapping("/hello")
    public String hello() {
        return "Hello GraalVM!"
    }
}


Build the application:

Shell
 
$ mvn clean verify
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  6.971 s
[INFO] Finished at: 2023-02-18T10:26:33+01:00
[INFO] ------------------------------------------------------------------------


As you can see in the output, it takes about seven seconds to build the Spring Boot application.

The target directory contains the jar-file mygraalvmplanet-0.0.1-SNAPSHOT.jar, which is about 17.6MB in size.

Start the application from the root of the repository:

Shell
 
$ java -jar target/mygraalvmplanet-0.0.1-SNAPSHOT.jar
2023-02-18T10:30:15.013+01:00  INFO 17233 --- [           main] c.m.m.MyGraalVmPlanetApplication         : Starting MyGraalVmPlanetApplication v0.0.1-SNAPSHOT using Java 17.0.6 with PID 17233 (/home/<user directory>/mygraalvmplanet/target/mygraalvmplanet-0.0.1-SNAPSHOT.jar started by <user> in /home/<user directory>/mygraalvmplanet)
...
2023-02-18T10:30:16.486+01:00  INFO 17233 --- [           main] c.m.m.MyGraalVmPlanetApplication         : Started MyGraalVmPlanetApplication in 1.848 seconds (process running for 2.212)


As you can see in the output, it takes 1.848 seconds to start the Spring Boot application.

With the help of top and the PID, which is logged in the first line of the output, you can check the CPU and memory consumption:

Shell
 
$ top -p 17233


The output shows that 0.3% CPU is consumed and 0.6% memory.

Create Native Image

In the previous section, you created and ran a Spring Boot application as you normally would do. In this section, you will create a native image of the Spring Boot application and run it as an executable.

Because you added the GraalVM Native Support dependency when creating the Spring Boot application, the following snippet is added to the pom file:

XML
 
<build>
  <plugins>
    <plugin>
      <groupId>org.graalvm.buildtools</groupId>
      <artifactId>native-maven-plugin</artifactId>
    </plugin>
    ...
  </plugins>
</build>


With the help of the native-maven-plugin, you can compile the native image by using the native Maven profile:

Shell
 
$ mvn -Pnative native:compile
...
[INFO] --- native-maven-plugin:0.9.19:compile (default-cli) @ mygraalvmplanet ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  8.531 s
[INFO] Finished at: 2023-02-05T16:50:20+01:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.graalvm.buildtools:native-maven-plugin:0.9.19:compile (default-cli) on project mygraalvmplanet: 'gu' tool wasn't found. This probably means that JDK at isn't a GraalVM distribution. -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException


The compilation fails because GraalVM is not used for the compilation. Let’s install GraalVM first.

Installing and switching between JDKs is fairly simple when you use SDKMAN. If you do not have any knowledge of SDKMAN, do check out a previous post.

Install GraalVM:

Shell
 
$ sdk install java 22.3.r17-nik


Use GraalVM in the terminal where you are going to compile:

Shell
 
$ sdk use java 22.3.r17-nik


Run the native build again:

Shell
 
$ mvn -Pnative native:compile
...
Produced artifacts:
 /home/<user directory>/mygraalvmplanet/target/mygraalvmplanet (executable)
 /home/<user directory>/mygraalvmplanet/target/mygraalvmplanet.build_artifacts.txt (txt)
========================================================================================================================
Finished generating 'mygraalvmplanet' in 2m 15s.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  02:27 min
[INFO] Finished at: 2023-02-18T10:48:40+01:00
[INFO] ------------------------------------------------------------------------


The build now takes about 2-5 minutes. Remember that the build without native compilation took about seven seconds. This is a huge increase in build time. This is due to the AOT compilation.

The target directory contains a mygraalvmplanet executable, which has a size of about 66.2MB. This is also an increase in size compared the jar-file, which was 17.6MB in size. But remember, the executable does not need a JVM to run, the jar-file does.

Start the Spring Boot application from the root of the repository:

Shell
 
$ target/mygraalvmplanet
2023-02-18T10:52:29.865+01:00  INFO 18085 --- [           main] c.m.m.MyGraalVmPlanetApplication         : Starting AOT-processed MyGraalVmPlanetApplication using Java 17.0.5 with PID 18085 (/home/<user directory>/mygraalvmplanet/target/mygraalvmplanet started by <user> in /home/<user directory>/mygraalvmplanet)
...
2023-02-18T10:52:29.920+01:00  INFO 18085 --- [           main] c.m.m.MyGraalVmPlanetApplication         : Started MyGraalVmPlanetApplication in 0.069 seconds (process running for 0.085)


If you blinked your eyes, you probably did not see it starting at all because the startup time is now 0.069 seconds. Compared to the 1.848 seconds without native compilation, this is almost 27 times faster.

When you take a look at the CPU and memory consumption with top, you notice the CPU consumption is negligable and the memory consumption is now 0.2% of the available memory, thus 3 times lower memory consumption.

Note: it is an executable now for a specific target platform.

Something About Reflection

GraalVM uses static analysis during compiling the classes. Only the classes that are being used in the application are analyzed. This means problems can arise when Reflection is being used. Spring makes extensive use of Reflection in their code and that was one of the reasons for the Spring Native project. A lot of Reflection has been removed from Spring. Besides that, it is possible to instruct GraalVM to add classes by means of a metadata file when GraalVM cannot find them during the static analysis. You can do so for your own application, but you do not have any influence on dependencies you are using. You can ask the maintainers to add the GraalVM metadata file, but they are not obliged to do so. To circumvent this issue and make the life of Spring developers more easy, Spring contributes to the GraalVM Reachability Metadata Repository and this repository is being consulted during the native compilation of your Spring Boot application.

Let’s see what happens when you add Reflection to your application.

Create a basic POJO, which you will use from within the HelloController by means of Reflection:

Java
 
public class Hello {
 
    private String message;
 
    public String getMessage() {
        return message;
    }
 
    public void setMessage(String message) {
        this.message = message;
    }
}


In the HelloController, you try to load the POJO by means of Reflection, set a hello message in the object, and return the hello message:

Java
 
@RestController
public class HelloController {
 
    @RequestMapping("/hello")
    public String hello() {
        String helloMessage = "Default message";
        try {
            Class<?> helloClass = Class.forName("com.mydeveloperplanet.mygraalvmplanet.Hello");
            Method helloSetMessageMethod = helloClass.getMethod("setMessage", String.class);
            Method helloGetMessageMethod = helloClass.getMethod("getMessage");
            Object helloInstance = helloClass.getConstructor().newInstance();
            helloSetMessageMethod.invoke(helloInstance, "Hello GraalVM!");
            helloMessage = (String) helloGetMessageMethod.invoke(helloInstance);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
        return helloMessage;
    }
}


Compile the application again to create a native image:

Shell
 
$ mvn -Pnative native:compile


Execute the image from within the root of the repository:

Shell
 
$ target/mygraalvmplanet


Invoke the hello endpoint:

Shell
 
$ curl http://localhost:8080/hello
Hello GraalVM!


And it just works! But how is this possible because we did not add a GraalVM metadata file? The answer can be found in the GraalVM documentation.

“The analysis intercepts calls to Class.forName(String), Class.forName(String, ClassLoader), Class.getDeclaredField(String), Class.getField(String), Class.getDeclaredMethod(String, Class[]), Class.getMethod(String, Class[]), Class.getDeclaredConstructor(Class[]), and Class.getConstructor(Class[]).

GraalVM will be able to add the necessary classes in the executable when one of the above calls are being used. In the example you used, these calls were being used and therefore the “Hello POJO” was added to the native image.

Conclusion

In this article, you learned how to create a GraalVM native image for a Spring Boot 3 application. You noticed the faster startup time, lower CPU, and memory consumption compared to using a jar-file in combination with a JVM. Some special attention is needed when Reflection is being used, but for many usages, GraalVM will be able to generate a complete native image.

Apache Maven GraalVM Java virtual machine Spring Boot Metadata

Published at DZone with permission of Gunter Rotsaert, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Comparing the Efficiency of a Spring Boot Project to a Go Project
  • Providing Enum Consistency Between Application and Data
  • Java’s Next Act: Native Speed for a Cloud-Native World
  • How to Build a New API Quickly Using Spring Boot and Maven

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!