BYO: Exploring Java with GraalVM 21 and Native Maven Plugin

Arpit RathoreArpit Rathore
5 min read

Java remains a powerhouse in the programming world, celebrated for its cross-platform capabilities, extensive ecosystem, and strong community support. But in recent years, there's been a growing emphasis on reducing application startup times and memory footprint—areas where Java's traditional JVM (Java Virtual Machine) falls short compared to natively compiled languages. This is where GraalVM and the native-maven-plugin shine, making Java a formidable contender in the realm of cloud-native and resource-efficient applications.

What is GraalVM?

GraalVM is a high-performance runtime that extends the Java Virtual Machine to support multiple programming languages, including JavaScript, Python, and Ruby. One of its standout features is the ability to compile Java applications into native executables. By compiling ahead-of-time (AOT), GraalVM eliminates the need for a JVM during runtime, resulting in significantly faster startup times and reduced memory usage.

The recent LTS version, GraalVM 21, builds upon its predecessors with enhanced features, improved performance, and better tooling support, making it an excellent choice for developers looking to optimize their Java applications.

Native Maven Plugin: Simplifying Native Builds

The native-maven-plugin is a bridge between Maven—Java’s most popular build tool—and GraalVM’s native-image capabilities. This plugin streamlines the process of compiling Java code into a native executable, automating many of the configurations and optimizations required for successful builds.

With the native-maven-plugin, developers can integrate GraalVM’s native-image generation directly into their Maven build lifecycle, making it easier to produce lightweight, high-performance native binaries.

Why Use GraalVM and Native Maven Plugin Together?

The combination of GraalVM and the native-maven-plugin addresses many challenges faced by modern Java developers:

  1. Reduced Startup Time: Native executables compiled by GraalVM start in milliseconds, a significant improvement over JVM-based applications.

  2. Lower Memory Usage: Native binaries use less memory, making them ideal for resource-constrained environments.

  3. Simplified Deployment: With no JVM dependency, native binaries can be deployed as standalone executables, simplifying distribution.

  4. Cloud-Native Compatibility: Faster startup and lower resource usage align with the demands of containerized applications and serverless architectures.

Getting Started with GraalVM and Native Maven Plugin

Bare minimum pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  <properties>
    <maven.compiler.source>21</maven.compiler.source>
    <maven.compiler.target>21</maven.compiler.target>
    <native-maven-plugin.version>0.10.4</native-maven-plugin.version>
    <main.class>com.arpitrathore.byo.App</main.class>
    <image.name>ar-helloworld</image.name>
  </properties>

  <profiles>
    <profile>
      <id>native</id>
      <build>
        <plugins>
          <plugin>
            <groupId>org.graalvm.buildtools</groupId>
            <artifactId>native-maven-plugin</artifactId>
            <version>${native-maven-plugin.version}</version>
            <extensions>true</extensions>
            <executions>
              <execution>
                <id>build-native</id>
                <phase>package</phase>
                <goals>
                  <goal>compile-no-fork</goal>
                </goals>
              </execution>
            </executions>
            <configuration>
              <mainClass>${main.class}</mainClass>
              <imageName>${image.name}</imageName>
              <agent>
                <enabled>true</enabled>
              </agent>
            </configuration>
          </plugin>
        </plugins>
      </build>
    </profile>
  </profiles>
</project>

The native-maven-plugin plugin integrates the native build process into the Maven lifecycle. It is configured under native maven profile and requires few configurations, such as the main class and the name of native binary output. Once configured, we can use maven commands to build the native binary:

$ mvn clean package -Pnative

# Output
Build resources:
 - 15.89GB of memory (67.9% of 23.42GB system memory, determined at start)
 - 4 thread(s) (100.0% of 4 available processor(s), determined at start)
[2/8] Performing analysis...  [****]                                                                    (19.5s @ 0.25GB)
    3,245 reachable types   (72.7% of    4,462 total)
    3,840 reachable fields  (50.2% of    7,657 total)
   15,730 reachable methods (45.5% of   34,552 total)
    1,025 types,    90 fields, and   676 methods registered for reflection
       57 types,    57 fields, and    52 methods registered for JNI access
        4 native libraries: dl, pthread, rt, z
[3/8] Building universe...                                                                               (2.9s @ 0.32GB)
[4/8] Parsing methods...      [**]                                                                       (2.4s @ 0.23GB)
[5/8] Inlining methods...     [***]                                                                      (1.6s @ 0.23GB)
[6/8] Compiling methods...    [[6/8] Compiling methods...    [****]                                                                    (18.2s @ 0.28GB)
[7/8] Layouting methods...    [**]                                                                       (2.4s @ 0.30GB)
[8/8] Creating image...       [**]                                                                       (2.8s @ 0.35GB)
   5.11MB (38.53%) for code area:     8,933 compilation units
   7.48MB (56.43%) for image heap:   97,328 objects and 47 resources
 684.84kB ( 5.04%) for other data
  13.26MB in total
------------------------------------------------------------------------------------------------------------------------
Top 10 origins of code area:                                Top 10 object types in image heap:
   3.84MB java.base                                            1.59MB byte[] for code metadata
 938.93kB svm.jar (Native Image)                               1.29MB byte[] for java.lang.String
 108.35kB java.logging                                       977.47kB java.lang.String
  56.84kB org.graalvm.nativeimage.base                       753.66kB java.lang.Class
  43.64kB jdk.proxy1                                         278.87kB com.oracle.svm.core.hub.DynamicHubCompanion
  42.03kB jdk.proxy3                                         278.63kB byte[] for general heap data
  21.98kB org.graalvm.collections                            242.67kB java.util.HashMap$Node
  19.52kB jdk.internal.vm.ci                                 217.99kB java.lang.Object[]
  10.46kB jdk.proxy2                                         185.82kB java.lang.String[]
   8.04kB jdk.internal.vm.compiler                           156.21kB byte[] for reflection metadata
   3.94kB for 3 more packages                                  1.58MB for 906 more object types
------------------------------------------------------------------------------------------------------------------------
Recommendations:
 INIT: Adopt '--strict-image-heap' to prepare for the next GraalVM release.
 HEAP: Set max heap for improved and more predictable memory usage.
 CPU:  Enable more CPU features with '-march=native' for improved performance.
------------------------------------------------------------------------------------------------------------------------
                        3.7s (6.7% of total time) in 350 GCs | Peak RSS: 0.76GB | CPU load: 3.50
------------------------------------------------------------------------------------------------------------------------
Produced artifacts:
 /home/arpit/build-your-own/01-hello-world/target/ar-helloworld (executable)
========================================================================================================================
Finished generating 'ar-helloworld' in 55.3s.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  59.522 s
[INFO] Finished at: 2025-01-07T00:48:43+05:30
[INFO] ------------------------------------------------------------------------

# Execute the native binary
$ ./target/ar-helloworld                                                                                          main main
Hello World! Current time is: 2025-01-07T00:48:45.178358

Use Cases for GraalVM Native Images

  • Microservices: Fast startup times and low memory consumption make GraalVM native images perfect for microservices in Kubernetes.

  • Serverless Computing: Instantaneous cold starts reduce latency in serverless environments.

  • CLI Applications: Lightweight, standalone binaries are ideal for command-line tools.

  • Edge Computing: The small footprint of native binaries makes them suitable for edge devices with limited resources.

Challenges and Considerations

While GraalVM and the native-maven-plugin offer exciting possibilities, there are some caveats:

  1. Compatibility Issues: Not all Java libraries are compatible with GraalVM’s native-image compilation due to reflection or dynamic class loading.

  2. Longer Build Times: Generating native images can take significantly longer than building traditional JARs.

  3. Configuration Overhead: Some applications require additional configurations (e.g., reflect-config.json) for successful native compilation.

Conclusion

GraalVM 21 and the native-maven-plugin are game-changers for Java developers aiming to build high-performance, resource-efficient applications. By leveraging these tools, you can modernize your Java projects and meet the demands of today’s cloud-native ecosystems.

Whether you’re building microservices, serverless functions, or lightweight tools, GraalVM and the native-maven-plugin open up a new world of possibilities. Start experimenting today and experience the future of Java development!

0
Subscribe to my newsletter

Read articles from Arpit Rathore directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Arpit Rathore
Arpit Rathore

Senior Backend Engineer