1. Overview

Spring Native for GraalVM provides support for compiling Spring to native executables using native-image compiler.

Compared to the Java Virtual Machine, GraalVM native offers advantages like the following, enabling cheaper and more sustainable hosting for certain kinds of workloads (microservices, functions) well suited for containers and Kubernetes:

  • Instant startup

  • Instant peak performance

  • Reduced memory consumption

But also a few drawbacks that could be reduced in the future as GraalVM native evolves:

  • Slower and heavy build

  • Less runtime optimizations after warmup

  • Less mature than the JVM with some different behaviors

The key differences between the regular JVM and this GraalVM native platform are:

  • A static analysis of your application from the main entry point is performed at build time.

  • The unused parts are removed at build time.

  • Configuration is required for reflection, resources, and dynamic proxies.

  • Classpath fixed at build time.

  • No class lazy loading: everything shipped in the executables will be loaded in memory at startup.

  • Some code will run at build time!

  • There are some limitations.

The goal of this project is to incubate the support for Spring Native, an alternative to Spring JVM, in order to provide a native deployment option designed to be packaged in lightweight containers. In practice, the target is to support your Spring application (typically Spring Boot ones), unmodified, on this new platform. Beware that we are not here yet, see the Support documentation for more details.

1.1. Modules

The Spring Native for GraalVM project is composed of the following modules:

  • spring-graalvm-native: this module aggregates the feature, configuration and substitutions ones to generate the artifact to consume.

  • spring-graalvm-native-feature: this module is a GraalVM feature. It is a kind of plugin for the native-image compilation process (which creates the native-image from the built class files). The feature participates in the compilation lifecycle, being invoked at different compilation stages to offer extra information about the application to aid in the image construction.

  • spring-graalvm-native-configuration: this module contains configuration hints for Spring classes, including various Spring Boot auto-configurations.

  • spring-graalvm-native-substitutions: this module allows patching (temporarily) some parts of Spring Boot and Spring Framework to improve the compatibility and efficiency of Spring native images.

  • spring-graalvm-native-samples: contains various samples that demonstrate the feature usage and are used as integration tests.

  • spring-graalvm-native-docs: contains the asciidoc documentation sources.

  • spring-graalvm-native-tools: tools used for reviewing image building configuration and output.

1.2. Modes

It supports the following modes using -Dspring.native.mode=<mode> on the native-image command line:

  • reflection (activated by default) provides resource, initialization, proxy and reflection (using auto-configuration hints) configuration for native images as well as substitutions.

  • init (manually activated) should be used if only wishing to provide initialization configuration and substitutions.

  • agent (experimental, activated when agent-generated config is present) is using the configuration generated by the tracing agent as a basis and also provides additional hints for components like controllers, etc.

  • functional (experimental, activated automatically when spring-fu or spring-init are in the classpath) is designed to work with functional bean registration. In this mode the feature will provide initialization, resource configuration, substitutions and spring.factories functional alternative (only Spring Boot spring.factories are supported for now).

2. Support

2.1. GraalVM

GraalVM version 20.3.0 is supported, see related release notes.

2.2. Language

Java 8, Java 11 and Kotlin 1.3+ are supported. Kotlin Coroutines are not yet supported.

2.3. Spring Boot

spring-graalvm-native 0.8.5 is designed to be used with Spring Boot 2.4.1.

The following starters are supported, the group ID is org.springframework.boot unless specified otherwise.

  • spring-boot-starter-actuator: WebMvc and WebFlux are supported, as well as metrics and tracing infrastructure. Beware that actuators significantly increase the footprint, this will be optimized in a future release.

    • --enable-https flag is required for HTTPS support.

  • spring-boot-starter-cache

  • spring-boot-starter-data-elasticsearch

    • --enable-https flag is required for HTTPS support.

  • spring-boot-starter-data-jdbc

    • --enable-all-security-services and -H:+AddAllCharsets may be required depending on your JDBC driver.

  • spring-boot-starter-data-jpa

  • spring-boot-starter-data-mongodb

  • spring-boot-starter-data-neo4j

    • --enable-https --enable-all-security-services are required to use any secure bolt connection (everything bolt+s(sc) or neo4j+s(sc).

  • spring-boot-starter-data-r2dbc

  • spring-boot-starter-data-redis

  • spring-boot-starter-jdbc

    • --enable-all-security-services and -H:+AddAllCharsets may be required depending on your JDBC driver.

  • spring-boot-starter-logging

  • spring-boot-starter-thymeleaf

  • spring-boot-starter-rsocket

  • spring-boot-starter-validation

  • spring-boot-starter-security: WebMvc and WebFlux form login, HTTP basic authentication and OAuth 2.0 are supported.

    • --enable-all-security-services flag is required.

  • spring-boot-starter-oauth2-resource-server: WebMvc and WebFlux are supported.

    • --enable-all-security-services flag is required.

  • spring-boot-starter-oauth2-client: WebMvc and WebFlux are supported.

    • --enable-all-security-services flag is required.

  • spring-boot-starter-webflux: only Netty is supported for now.

    • --enable-https flag is required for HTTPS support.

  • spring-boot-starter-web:

    • Only Tomcat is supported for now.

    • tomcat-embed-programmatic dependency can be used instead of tomcat-embed-core and tomcat-embed-websocket ones for optimized footprint. If you are using spring-boot-starter-web and Maven, you should use the following pom.xml configuration:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-websocket</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>org.apache.tomcat.experimental</groupId>
    <artifactId>tomcat-embed-programmatic</artifactId>
    <version>${tomcat.version}</version>
</dependency>
  • --enable-https flag is required for HTTPS support.

    • spring-boot-starter-websocket

    • com.wavefront:wavefront-spring-boot-starter

  • --enable-https flag is required.

2.4. Spring Cloud

spring-graalvm-native 0.8.5 is designed to be used with Spring Cloud 2020.0.0-RC1.

Group ID is org.springframework.cloud.

  • spring-cloud-starter-bootstrap

  • spring-cloud-starter-config

  • spring-cloud-config-server

  • spring-cloud-starter-netflix-eureka-client

  • spring-cloud-starter-netflix-eureka-server

  • spring-cloud-function-web

    • --enable-https flag is required for HTTPS support.

  • spring-cloud-function-adapter-aws

  • spring-cloud-starter-function-webflux

    • --enable-https flag is required for HTTPS support.

2.5. Others

2.6. Limitations

In native applications, @Bean annotated methods do not support cross @Bean invocations since they require a CGLIB proxy created at runtime. This is similar to the behavior you get with the so called lite mode or with @Configuration(proxyBeanMethods=false). It is fine for applications to just use @Configuration without setting proxyBeanMethods=false and use method parameters to inject bean dependencies. Libraries are encouraged to use @Configuration(proxyBeanMethods=false) (most of Spring portfolio currently uses this variant) since it is generally a good idea to avoid CGLIB proxies if not needed and to provide native compatibility. This behavior could potentially become the default in a future Spring Framework version.

Only proxies on interfaces are supported for now.

3. Getting Started

There are two main ways to build Spring Boot native application:

3.1. Getting started with Buildpacks

This section walks through how to build a Spring Boot native application with Buildpacks support. This is a practical guide, so we perform this for real on the REST service getting started guide.

3.1.1. Prerequisites

You will need Docker, see docs.docker.com/installation/#installation for details on setting Docker up for your machine. Before proceeding further, verify you can run docker commands from the shell.

On Mac and Windows, it is recommended to increase the memory allocated to Docker to at least 8G (and potentially to add more CPUs as well) since native-image compiler is a heavy process. See this Stackoverflow answer for more details. On Linux, Docker uses by default the resources available on the host so no configuration is needed.

3.1.2. Set up the sample project

Like the instructions for using the feature, here we use the getting started REST service guide. This is the sample project we trace with the agent and then build into a native image. The following commands install the REST service guide:

git clone https://github.com/spring-guides/gs-rest-service
cd gs-rest-service/complete

You may already be ready to go with your own project.

Update the build file
Only Spring Boot 2.4.1 is supported, upgrade your project if necessary.
Maven
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.1</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
Gradle Groovy
plugins {
    id 'org.springframework.boot' version '2.4.1'
}
Gradle Kotlin
plugins {
    id("org.springframework.boot") version "2.4.1"
}
Configure the plugin

Spring Boot Buildpacks support provides out of the box support for building Spring Boot native applications. To do so, we just have to set BP_BOOT_NATIVE_IMAGE environment variable to true and provide optionally additional build arguments.

Maven
<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <image>
            <builder>paketobuildpacks/builder:tiny</builder>
            <env>
                <BP_BOOT_NATIVE_IMAGE>true</BP_BOOT_NATIVE_IMAGE>
                <BP_BOOT_NATIVE_IMAGE_BUILD_ARGUMENTS>
                    -Dspring.native.remove-yaml-support=true
                    -Dspring.spel.ignore=true
                </BP_BOOT_NATIVE_IMAGE_BUILD_ARGUMENTS>
            </env>
        </image>
    </configuration>
</plugin>
Gradle Groovy
bootBuildImage {
    builder = "paketobuildpacks/builder:tiny"
    environment = [
        "BP_BOOT_NATIVE_IMAGE" : "1",
        "BP_BOOT_NATIVE_IMAGE_BUILD_ARGUMENTS": "-Dspring.spel.ignore=true -Dspring.native.remove-yaml-support=true"
    ]
}
Gradle Kotlin
tasks.getByName<BootBuildImage>("bootBuildImage") {
    builder = "paketobuildpacks/builder:tiny"
    environment = mapOf(
            "BP_BOOT_NATIVE_IMAGE" to "1",
            "BP_BOOT_NATIVE_IMAGE_BUILD_ARGUMENTS" to "-Dspring.spel.ignore=true -Dspring.native.remove-yaml-support=true"
    )
}

-Dspring.native.remove-yaml-support=true and -Dspring.spel.ignore=true are optional optimization flags to reduce the footprint.

Notice we are using the paketobuildpacks/builder:tiny variant of Paketo Buildpacks, paketobuildpacks/builder:base being the default (also works but less optimized). The paketobuildpacks/builder:full variant can be used in order to have more tools during the development.

Add the repositories

If necessary, add the repository for the spring-graalvm-native dependency, as follows:

Maven
<repositories>
    <repository>
        <id>spring-milestone</id>
        <name>Spring milestone</name>
        <url>https://repo.spring.io/milestone</url>
    </repository>
</repositories>
Gradle Groovy
repositories {
    maven { url 'https://repo.spring.io/milestone' }
}
Gradle Kotlin
repositories {
    maven { url = uri("https://repo.spring.io/milestone") }
}

And for plugins:

Maven
<pluginRepositories>
    <pluginRepository>
        <id>spring-milestone</id>
        <name>Spring milestone</name>
        <url>https://repo.spring.io/milestone</url>
    </pluginRepository>
</pluginRepositories>
Gradle Groovy
pluginManagement {
    repositories {
        maven { url 'https://repo.spring.io/milestone' }
    }
}
Gradle Kotlin
pluginManagement {
    repositories {
        maven { url = uri("https://repo.spring.io/milestone") }
    }
}
spring-graalvm-native dependency

If not specified, the builder will automatically use the latest spring-graalvm-native release available, but here to be sure of the version used we specify it explicitly:

Maven
<dependencies>
    <dependency>
        <groupId>org.springframework.experimental</groupId>
        <artifactId>spring-graalvm-native</artifactId>
        <version>0.8.5</version>
    </dependency>
</dependencies>
Gradle Groovy
dependencies {
    implementation 'org.springframework.experimental:spring-graalvm-native:0.8.5'
}
Gradle Kotlin
dependencies {
    implementation("org.springframework.experimental:spring-graalvm-native:0.8.5")
}

3.1.3. Build the native application

Building the native application is as simple as running:

Maven
mvn spring-boot:build-image
Gradle Groovy
gradle bootBuildImage
Gradle Kotlin
gradle bootBuildImage

This will create a Linux container to build the native application using GraalVM native image compiler and deploy locally the related container image.

3.1.4. Run the application

To run your application, you need to run the previously created container image:

docker run -p 8080:8080 docker.io/library/rest-service:0.0.1-SNAPSHOT

As an alternative, you can also write a docker-compose.yml at the root of the project with the following content:

version: '3.1'
services:
  rest-service:
    image: rest-service:0.0.1-SNAPSHOT

And then run

docker-compose up

The startup time is <100ms, compared ~1500ms when starting the fat jar.

Now that the service is up, visit localhost:8080/greeting, where you should see:

{"id":1,"content":"Hello, World!"}

3.2. Getting started with native image Maven plugin

This section walks through how to build a Spring Boot native application with native image Maven plugin. This is a practical guide, so we perform this for real on the REST service getting started guide.

3.2.1. Prerequisites

You need a local install of native image compiler.

From GraalVM builds:

  • Install GraalVM 20.3.0 from here (dev builds are also available from here).

  • Set JAVA_HOME and PATH appropriately for that GraalVM version.

  • Run gu install native-image to bring in the native-image extensions to the JDK.

Or you can use SDKMAN to easily switch between GraalVM versions:

  • Install SDKMAN

  • Install GraalVM with sdk install java 20.3.0.r8-grl for Java 8 or sdk install java 20.3.0.r11-grl for Java 11

  • Run gu install native-image to bring in the native-image extensions to the JDK.

3.2.2. Set up the sample project

Like the instructions for using the feature, here we use the getting started REST service guide. This is the sample project we trace with the agent and then build into a native image. The following commands install the REST service guide:

git clone https://github.com/spring-guides/gs-rest-service
cd gs-rest-service/complete

You may already be ready to go with your own project.

Update the pom.xml file
Only Spring Boot 2.4.1 is supported, upgrade your project if necessary.
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.1</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
Add the Maven plugin

GraalVM provides a Maven plugin. Paste the following XML into the pom.xml file (we use it later to invoke the native image build):

<profiles>
  <profile>
    <id>native</id>
    <build>
      <plugins>
        <plugin>
          <groupId>org.graalvm.nativeimage</groupId>
          <artifactId>native-image-maven-plugin</artifactId>
          <version>20.3.0</version>
          <configuration>
            <mainClass>com.example.restservice.RestServiceApplication</mainClass>
            <buildArgs>-Dspring.native.remove-yaml-support=true -Dspring.spel.ignore=true</buildArgs>
          </configuration>
          <executions>
            <execution>
              <goals>
                <goal>native-image</goal>
              </goals>
              <phase>package</phase>
            </execution>
          </executions>
        </plugin>
        <plugin>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
      </plugins>
    </build>
  </profile>
</profiles>

The native image build needs to know the entry point to your application hence the <mainClass> element.

Even if declared outside of native image profile, spring-boot-maven-plugin need to be redeclared in the native Maven profile in order to work correctly with native-image-maven-plugin.

-Dspring.native.remove-yaml-support=true and -Dspring.spel.ignore=true are optional optimization flag to reduce the footprint.

Add the repositories

If necessary, add the repository for the spring-graalvm-native dependency, as follows:

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

And for plugins:

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

With native-image-maven-plugin, the spring-graalvm-native dependency should be specified explicitly:

<dependencies>
    <!-- ... -->
    <dependency>
        <groupId>org.springframework.experimental</groupId>
        <artifactId>spring-graalvm-native</artifactId>
        <version>0.8.5</version>
    </dependency>
</dependencies>

3.2.3. Build the native application

Building the native application is as simple as running:

mvn -Pnative clean package

This will create a native executable containing your Spring Boot application.

3.2.4. Run the application

To run your application, you need to run the previously created container image:

target/com.example.restservice.restserviceapplication

The startup time is <100ms, compared ~1500ms when starting the fat jar.

Now that the service is up, visit localhost:8080/greeting, where you should see:

{"id":1,"content":"Hello, World!"}

4. Samples

There are numerous samples in the spring-graalvm-native-samples subfolder of the root project. These show the feature in use with different technologies.

Most of them use Spring Boot Buildpacks support (Maven for most of the sample except the webmvc-kotlin one which is using Gradle). There are also build.sh and other related Bash scripts used for development and CI.

Beware that native image compilation can takes a long time and eats RAM.

The samples show the wide variety of tech that is working fine: Spring MVC with Tomcat, Spring WebFlux with Netty, Thymeleaf, JPA, and others. The Petclinic samples brings multiple technologies together in one application.

commandlinerunner-jafu and webmvc-jafu samples are based on the Spring Fu experimental project and are designed to demonstrate how leveraging Spring infrastructure in a functional way ends up to smaller native images that consume less memory. webmvc-functional sample is based on the Spring Init experimental project and is designed to demonstrate how annotation based configuration can be turned to functional one to get similar benefits than with Spring Fu.

If you are starting to build your first Spring Boot application, we recommend you follow one of the getting started guides.

5. Options

You can tweak the behavior of GraalVM native by setting options. These are passed to native-image as command line parameters, in a META-INF/native-image/native-image.properties file with an Args key or through the <buildArgs>..</buildArgs> block when invoking with Maven.

5.1. GraalVM options

GraalVM offers options that are enabled by default and others that are not enabled by default (some of which are recommended, though).

5.1.1. Options enabled by default

These options are enabled by default when using spring-graalvm-native, since they are mandatory to make a Spring application work when compiled as GraalVM native images.

  • --allow-incomplete-classpath allows image building with an incomplete class path and reports type resolution errors at run time when they are accessed the first time, instead of during image building.

  • --report-unsupported-elements-at-runtime reports usage of unsupported methods and fields at run time when they are accessed the first time, instead of as an error during image building.

  • --no-fallback enforces native image only runtime and disable fallback on regular JVM.

  • --no-server means do not use the image-build server which can be sometimes unreliable, see graal#1952 for more details.

  • --install-exit-handlers allows to react to a shutdown request from Docker.

Notice that XML and JMX support is disable by default, see options bellow to enable them if needed.

  • --verbose makes image building output more verbose.

  • -H:+ReportExceptionStackTraces provides more detail should something go wrong.

5.1.3. Other options

  • --initialize-at-build-time initializes classes by default at build time without any class or package being specified. This option is currently (hopefully, temporarily) required for Netty-based applications but is not recommended for other applications, since it can trigger compatibility issues, especially regarding logging and static fields. See this issue for more details. You can use it with specific classes or package specified if needed.

  • -H:+PrintAnalysisCallTree helps to find what classes, methods, and fields are used and why. You can find more details in GraalVM reports documentation.

  • -H:ReportAnalysisForbiddenType=com.example.Foo helps to find why the specified class is included in the native image.

  • -H:+TraceClassInitialization provides useful information to debug class initialization issues.

5.2. Spring Framework/Boot options

  • -Dspring.spel.ignore=true removes SpEL support, enabling faster compilation and smaller executables.

  • -Dspring.xml.ignore=false restores XML support (removed by default).

5.3. Spring Native for GraalVM options

The current feature options are as follows:

  • -Dspring.native.mode=reflection|init|agent|functional switches how much configuration the feature actually provides to native-image.

    • reflection (activated by default) provides resource, initialization, proxy and reflection (using auto-configuration hints) configuration for native images as well as substitutions.

    • init (manually activated) should be used if only wishing to provide initialization configuration and substitutions.

    • agent (experimental, activated when agent-generated config is present) is using the configuration generated by the tracing agent as a basis and also provides additional hints for components like controllers, etc.

    • functional (experimental, activated when spring-fu or spring-init are in the classpath) is designed to work with functional bean registration. In this mode the feature will provide initialization, resource configuration, substitutions and spring.factories functional alternative (only Spring Boot spring.factories are supported for now).

  • -Dspring.native.verbose=true outputs lots of information about the feature behavior as it processes auto-configuration and chooses which to include.

  • -Dspring.native.remove-jmx-support=false restores Spring Boot JMX support (removed by default).

  • -Dspring.native.remove-yaml-support=true removes Spring Boot Yaml support, enabling faster compilation and smaller executables.

  • -Dspring.native.dump-config=/tmp/dump.txt dumps the configuration to the specified file.

  • -Dspring.native.verify=false switches off the verifier mode (on by default). See the Limitations section for more details on this experimental option.

  • -Dspring.native.missing-selector-hints=warning switches the feature from a hard error for missing hints to a warning. See the Troubleshooting section for more details on this.

  • -Dspring.native.remove-unused-autoconfig=false disables removal of unused configurations.

  • -Dspring.native.fail-on-version-check=false disables failure on Spring Boot version check.

  • -Dspring.native.factories.no-actuator-metrics=true disables inclusion of metrics related actuator infrastructure when importing the actuator starter. Large reduction in memory usage with this turned on (obviously only usable if the application does not require that infrastructure).

  • -Dspring.native.build-time-properties-checks= switches on build time evaluation of some configuration conditions related to properties. It must include at least an initial setting of default-include-all or default-exclude-all and that may be followed by a comma separated list of prefixes to explicitly include or exclude (for example =default-include-all,!spring.dont.include.these.,!or.these or =default-exclude-all,spring.include.this.one.though.,and.this.one). When considering a property the longest matching prefix in this setting will apply (in cases where a property matches multiple prefixes).

  • -Dspring.native.build-time-properties-match-if-missing=false means for any properties specifying matchIfMissing=true that will be overridden and not respected. This does flip the application into a mode where it needs to be much more explicit about specifying properties that activate configurations. (This is a work in dev option really for experimenting with image size vs explicit property trade offs).

5.4. Other options

  • --enable-all-security-services required for crypto and some security services.

  • --enable-http enables HTTP support.

  • --enable-https enables HTTPS support.

5.5. Unsupported options

  • --initialize-at-build-time without class or package specified is not supported since Spring Native for GraalVM is designed to work with runtime class initialization by default (a selected set of classes are enabled at buildtime).

6. Tracing agent

When using the agent to compute configuration for native-image, there are a couple of approaches:

  • launch the app directly and exercise it

  • run application tests to exercise the application

The first option is certainly quick but rather manual/tedious. The second option sounds much more appealing for a robust/repeatable setup but by default the generated configuration will include anything required by the test infrastructure, which is unnecessary when the application runs for real. To address this problem the agent supports an access-filter file that will cause certain data to be excluded from the generated output.

6.1. Testing with the agent to compute configuration

6.1.1. A basic access-filter file

This is a simple access-filter.json file.

{ "rules": [
  {"excludeClasses": "org.apache.maven.surefire.**"},
  {"excludeClasses": "net.bytebuddy.**"},
  {"excludeClasses": "org.apiguardian.**"},
  {"excludeClasses": "org.junit.**"},
  {"excludeClasses": "org.mockito.**"},
  {"excludeClasses": "org.springframework.test.**"},
  {"excludeClasses": "org.springframework.boot.test.**"},
  {"excludeClasses": "com.example.demo.test.**"}
  ]
}

Most of these lines would apply to any Spring application, except for the last one which is application specific and will need tweaking to match the package of a specific applications tests.

6.1.2. Using the access-filter file

The access-filter.json file is specified with the access-filter-file option as part of the agentlib string:

-Dorg.graalvm.nativeimage.imagecode=true -agentlib:native-image-agent=access-filter-file=access-filter.json,config-output-dir=target/classes/META-INF/native-image

Note the inclusion of the -D to set the org.graalvm.nativeimage.imagecode property. In normal operation a built native executable will have this property set (automatically by the image building process). If there is any code that will behave differently due to it being set (which may occur if trying to work around something not supported by GraalVM) then we should set it when running with the agent too, so that code executed whilst the agent is attached matches the code that will run in the final executable.

6.1.3. Using it with maven

Let’s look at how to pull the ideas here together and apply them to a project.

Since Spring takes an eager approach when building the application context, a very basic test that starts the application context will exercise a lot of the Spring infrastructure that needs to produce native-image configuration. This test would suffice for that and could be placed in src/test/java:

package com.example.demo.test;

import org.junit.jupiter.api.Test;

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class AppContextStartupTest {

	@Test
	public void contextLoads() {
	}

}

Now take the access-filter.json file from above and place it in src/main/resources folder.

Finally, this following snippet would go into the maven pom:

    <profiles>
    ...
       <profile>
            <id>native</id>
            <build>
                <plugins>
                    <plugin>
                        <artifactId>maven-antrun-plugin</artifactId>
                        <executions>
                            <execution>
                                <id>make-native-image-config-folder</id>
                                <phase>test-compile</phase>
                                <configuration>
                                    <target>
                                        <mkdir dir="target/classes/META-INF/native-image"/>
                                    </target>
                                </configuration>
                                <goals>
                                    <goal>run</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-surefire-plugin</artifactId>
                        <configuration>
                            <argLine>-Dorg.graalvm.nativeimage.imagecode=agent -Dspring.xml.ignore=true -Dspring.spel.ignore=true -agentlib:native-image-agent=access-filter-file=target/classes/access-filter.json,config-merge-dir=target/classes/META-INF/native-image</argLine>
                            <includes>
                              <include>**/AppContextStartupTest.java</include>
                            </includes>
                        </configuration>
                    </plugin>
                    <plugin>
                        <groupId>org.graalvm.nativeimage</groupId>
                        <artifactId>native-image-maven-plugin</artifactId>
                        <version>20.3.0</version>
                        <configuration>
                            <mainClass>com.example.demo.DemoApplication</mainClass>
                            <imageName>example-compiled-app</imageName>
                            <buildArgs>-Dspring.native.remove-yaml-support=true -Dspring.spel.ignore=true</buildArgs>
                        </configuration>
                        <executions>
                            <execution>
                                <goals>
                                    <goal>native-image</goal>
                                </goals>
                                <phase>package</phase>
                            </execution>
                        </executions>
                    </plugin>
                    <plugin>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-maven-plugin</artifactId>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>

What does all of that do:

  • make-native-image-config-folder creates the target folder for the agent generated configuration

  • maven-surefire-plugin is used to run the single test above (in this case) to start/stop the application context. Notice the test is run with the -agentlib specified including the access-filter.json option and the target folder for the generated configuration.

  • native-image-maven-plugin will invoke native-image with the computed configuration. Update the <imageName>example-compiled-app</imageName> to give a name to the native-image built executable. Also tweak the <mainClass>…​</mainClass> to point to the main application class.

Then, running mvn -Pnative clean package will do everything from end-to-end. It will compile the app, run the test with agent attached, finally building the native-image executable.

7. Troubleshooting

While trying to build native images, various things can go wrong, either at image-build time or at runtime when you try to launch the built image. Usually, the problem is a lack of configuration. GraalVM native-image compiler probably was not told about the application intending to reflectively call something or load a resource. However, there can also be more serious problems, the native-image crashing or the application using a third party library that includes code not compatible with native-image.

This section explores some of the errors that can encountered and possible any fixes or workarounds. The two main sections of this page are problems at image-build time and problems at image runtime. Native image reference documentation is also worth to read in details.

The Spring team is actively working with the GraalVM team on issues. You can see the currently open issues here.

7.1. native-image is failing

The image can fail for a number of reasons. We have described the most common causes and their solutions here.

7.1.1. DataSize was unintentionally initialized at build time

If you see an error like:

Error: Classes that should be initialized at run time got initialized during image building:
 org.springframework.util.unit.DataSize was unintentionally initialized at build time. To see why org.springframework.util.unit.DataSize got initialized use -H:+TraceClassInitialization

You have probably tried to compile a Spring Boot native application without the spring-graalvm-native dependency. Add such dependency to the classpath and this issue should be gone.

7.1.2. Out of memory error when building the native image

native-image consumes a lot of RAM, we recommend a machine with at least 16G of RAM.

7.2. The built image does not run

If your built image does not run, you can try a number of fixes. This section describes those possible fixes.

7.2.1. Missing resource bundles

In some cases, when there is a problem, the error message tries to tell you exactly what to do, as follows:

Caused by: java.util.MissingResourceException:
  Resource bundle not found javax.servlet.http.LocalStrings.
  Register the resource bundle using the option
    -H:IncludeResourceBundles=javax.servlet.http.LocalStrings.

Here, it is clear we should add -H:IncludeResourceBundles=javax.servlet.http.LocalStrings. See related documentation for more details.

7.2.2. Missing configuration

The feature will do the best it can to catch everything but it doesn’t understand every bit of code out there. In these situations you can write manual configuration yourself, or use the agent to collect what is likely missing. In this setup you run the application as normal (on a JVM) with an agent attached. As the application runs the agent collects information (what the application is reflecting on, etc) and outputs it to a folder in a format suitable for inclusion in the next native-image run. The agent configuration can be used with the feature. This may not produce the most optimal image, but it may produce a working image - and that is a great start.

Create a location to contain the collected config

This can be anywhere but the location must be on the classpath for the native-image build. It can be useful to put it into the application resources area as this will be on the classpath by default when the native executable build step occurs.

mkdir -p src/main/resources/META-INF/native-image

Run with the agent

Now simply run the application, with the following addition to the Java command to collect configuration in that folder:

-agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image

When the next native-image run happens it should automatically additionally include this collected configuration.

7.2.3. Getting ClassNotFoundException

The following listing shows a typical ClassNotFoundException:

Caused by: java.lang.ClassNotFoundException:
  org.apache.catalina.authenticator.jaspic.AuthConfigFactoryImpl
	at com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:60) ~[na:na]

This exception indicates the native-image build was not told about the reflective need for a type and, so, did not include the data to satisfy a request to access it at runtime.

If you use the agent, it can be a sign that the agent is missing something (this particular message was collected while debugging issue 2198).

You have a number of options to address it:

  • Download a new version of GraalVM that includes a fixed agent.

  • Raise a bug against the spring-graalvm-native project, as a key aim of the feature is to make sure these things get registered. If it is not “obvious” that it should be registered, it may be necessary for a new or expanded hint to the added to the spring-graalvm-native-configuration project (see the how to contribute guide if you want to explore that).

  • Manually add it. The native-image run picks up any configuration it finds. In this case, you can create a reflect-config.json under a META-INF/native-image folder structure and ensure that is on the classpath. This is sometimes most easily accomplished by creating it in your project src/main/resources folder (so, src/main/resources/META-INF/native-image/reflect-config.json). Note that there are various access options to specify. To fix it, we set allDeclaredConstructors and allDeclaredMethods. We might need allDeclaredFields if the code intends to reflect on those, too. The following entry satisfies the preceding error:

[
{
 "name":"org.apache.catalina.authenticator.jaspic.AuthConfigFactoryImpl",
 "allDeclaredConstructors":true,
 "allDeclaredMethods":true
}
]

Recompiling should pick up this extra configuration, and the resultant image include metadata to satisfy reflection of AuthConfigFactoryImpl.

7.3. These methods are directly invoking methods marked @Bean

Rather than simply failing with a difficult to debug error, there is a verification system in the feature that will try to give early warnings and errors of what might be the problem. This mode is turned on by default and can be disabled via the option -Dspring.native.verify=false. Currently, it raises an error about cross @Bean method invocations, see Limitations for more details.

7.3.1. No access hint found for import selector: XXX

The feature chases down configuration references to other configurations (@Import usages). However if you use an import selector, that means code is deciding what the next imported configuration should be, which is harder to follow. The feature does not do that level of analysis (it could get very complicated). This means that, although the feature can tell it has encountered a selector, it does not know what types that selector needs reflective access to or what further configurations it references. Now, the feature could continue. Maybe it would work, and maybe it would crash at runtime. Typically, the error you get can when there is a missing hint can be very cryptic. If the selector is doing a “if this type is around, return this configuration to include”, it may be not finding some type (when it is really there but is not exposed in the image) and not including some critical configuration. For this reason, the feature fails early and fast, indicating that it does not know what a particular selector is doing. To fix it, take a look in the selector in question and craft a quick hint for it. See this commit that was fixing this kind of problem for a Spring Security (issue).

you can temporarily turn this hard error into a warning. It is possible that, in your case, you do not need what the selector is doing. To do so, specify the -Dspring.native.missing-selector-hints=warning option to cause log messages about the problem but not a hard fail. Note that using warnings rather than errors can cause serious problems for your application.

7.4. Working with snapshots

Snapshots are regularly published and obviously ahead of releases and milestones. If you wish to use the snapshot versions you should use the following repository:

<repositories>
    <!-- ... -->
    <repository>
        <id>spring-snapshots</id>
        <name>Spring Snapshots</name>
        <url>https://repo.spring.io/snapshot</url>
    </repository>
</repositories>

7.5. Diagnosing issues with the feature

Sometimes, you want to use the feature but cannot. Maybe you like that the feature offers that more optimal mode of discarding unnecessary configuration at image-build time, which the agent mode does not. When you use the feature, you either get an error about some missing piece of configuration or, worse, you get no error and it does not work (implying there is probably missing configuration that is not critical for the app to start but is just critical for it to actually work). If the error is clear, you can follow the guidelines in the how to contribute guide and perhaps contribute it back. But in the case where you have no idea, what do you do?

The first step to take here is try and run it with the agent, as follows:

mkdir -p native-image-config
mvn clean package
java -agentlib:native-image-agent=config-output-dir=native-image-config \
  -jar target/myapp-0.0.1-SNAPSHOT.jar

After hitting the application through whatever endpoints you want to exercise and shutting it down, there should be config files in the output folder, as follows:

ls -l native-image-config
-rw-r--r--  1 foo bar    135 26 Mar 11:25 jni-config.json
-rw-r--r--  1 foo bar    277 26 Mar 11:25 proxy-config.json
-rw-r--r--  1 foo bar  32132 26 Mar 11:25 reflect-config.json
-rw-r--r--  1 foo bar    461 26 Mar 11:25 resource-config.json

Now, we want to compare native-image-config/reflect-config.json with the configuration being produced by the feature. Luckily, the feature supports a dump mode, where it puts it out on disk for us to see. Add the following to the maven <buildArgs>…​</buildArgs> section or as a parameter in the direct call to native-image:

-Dspring.native.dump-config=/a/b/c/feature-reflect-config.json

Then, after running the native image build again, that file should exist. It is now possible to diff the computed one with the agent one. The scripts folder in spring-graalvm-native contains a compare script, which you can invoke as follows:

~/spring-graalvm-native/scripts/reflectCompare.sh feature-reflect-config.json native-image-config/reflect-config.json > diff.txt

This script produces a summary of the differences. It understands the format a little better than doing a plain diff:

$ tail diff.txt
...

Summary:
In first but not second: 395
In second but not first: 69
In both files but configured differently: 51
In both files and configured the same: 67

We might search that for entries are in the agent file that are not in the computed file for Spring, as follows:

grep "^> org.spring" diff.txt

This shows data similar to the following:

> org.springframework.context.ApplicationEventPublisherAware setFlags:[allPublicMethods]
> org.springframework.context.ApplicationListener setFlags:[allPublicMethods]
> org.springframework.context.EnvironmentAware setFlags:[allPublicMethods]
> org.springframework.context.SmartLifecycle setFlags:[allPublicMethods]
> org.springframework.core.annotation.AliasFor setFlags:[allDeclaredMethods]
> org.springframework.core.annotation.SynthesizedAnnotation

You can craft these into a config file for the project, as follows:

mkdir -p src/main/resources/META-INF/native-image

Now create src/main/resources/META-INF/native-image/reflect-config.json with content similar to the following (including the first one from the diff in this example):

[
{"name":"org.springframework.context.ApplicationEventPublisherAware","allPublicMethods":true}
]

As we add the details found in the diff, we can rebuild the native-image each time and see which bits help. Once computed, we can create a hint in the feature configuration project that captures this knowledge (see the how to contribute guide for more info on that) or, if it is more related to this specific application than the infrastructure, we might leave that reflect-config.json in the project and commit it to our repository alongside the source for future use.

8. How to Contribute

This section describes how to extend Spring Native for GraalVM. You can then submit pull requests in order to add support for a specific part of Spring ecosystem.

8.1. Using container-based build environment

In order to allow easily reproducible builds of spring-graalvm-native, dedicated interactive Docker images are available for local development (tested on Linux and Mac) and are also used in the CI:

  • graalvm-ce: base image with Ubuntu bionic + GraalVM native, built daily by the CI and available from Docker hub or locally via docker/build-graalvm-ce-images.sh.

  • spring-graalvm-native: base image with graalvm-ce + utilities required to build the project, available from Docker hub or locally via docker/build-spring-graalvm-native-images.sh.

  • spring-graalvm-native-dev: local image built via docker/build-dev-images.sh designed to share the same user between the host and the container

To use it:

  • Install Docker.

  • Configure it to allow non-root user if you are on Linux.

  • On Mac, ensure in the Docker preferences resources tab that you give it enough memory, ideally 10G or more, otherwise you may see out of memory issues when building images.

  • Run run-dev-container.sh to run the Docker container with an interactive shell suitable to run spring-graalvm-native build scripts (see bellow for more documentation).

  • The first time, it will download remote based images built by the CI.

  • The current and the Maven home directories are shared between the host (where is typically the IDE) and the container (where you can run builds).

8.1.1. run-dev-container.sh

run-dev-container.sh runs Spring Native for GraalVM dev container with an interactive shell.

run-dev-container.sh [options]

options:
-h, --help                show brief help
-j, --java=VERSION        specify Java version to use, can be 8 or 11, 8 by default
-g, --graalvm=VERSION     specify GraalVM version to use, can be 20.1-dev or master, 20.1-dev by default
-w, --workdir             specify the working directory, should be an absolute path, current one by default
-p, --pull                force pulling of remote container images
-r, --rebuild             force container image rebuild

8.1.2. Usual dev workflow

  • Import the root project in your IDE.

  • Eventually import the sample you are working on as a distinct project in your IDE.

  • Run run-dev-container.sh to run the Docker container with an interactive shell.

  • Run the root project build.sh (from the host or the container) if you have made modification to the feature, substitutions or configuration modules.

  • Run build.sh of the sample you are working on from the container.

To test the various samples You can also run the root build.sh then build-key-samples.sh (test only key samples) or build-samples.sh (test all samples) from the container.

8.2. Extending the feature

We are experimenting with extension models, as you will see if you look in the spring-graalvm-native-configuration module within the project. Giant .json files are a little unwieldy, and the structure of the Spring Boot autoconfigure module means many types would need to be mentioned in a .json file for that module, even though only a fraction of the autoconfigurations are likely to be active in a single application, so not all the entries in the json file would be necessary.

What the configuration module is trying to achieve is to tie the access to certain types to the Spring configuration that needs them. Then, for any given application, if we know the likely active configurations, we can expose only the types most likely to be needed.

This section walks through the structure of this hint data and finishes with how to add more configuration for areas we have not yet looked at. Perhaps you want to work on one and submit it as a PR.

8.3. Hints

So the giant .json file the feature project used to include has been decomposed into lots of @NativeImageHint annotations. Each of these hint specifies some native image configuration (perhaps a resource that needs to be included in the image, or some types that need reflective access at runtime), and optionally, a trigger. The trigger might be piece of Spring infrastructure (like an autoconfiguration) or just a regular class. If the feature determines that Spring infrastructure may be active when the application runs, or (for a regular class trigger) that the named class is on the classpath, it will activate the associated hints, informing the native-image build process what is needed.

These @NativeImageHint can be hosted in one of two places.

First, in the spring-graalvm-native-configuration module, you can see that they are hosted on types that implement the org.springframework.nativex.extension.NativeImageConfiguration interface (defined by the feature). Implementations of this interface should be listed in a src/main/resources/META-INF/services/org.springframework.nativex.extension.NativeImageConfiguration file, which the feature loads through regular Java service loading.

Secondly, they can be put directly onto Spring configuration classes, and they are picked up by the feature.

In this way, if you are experimenting with making your application into a native image, you can keep the hint configuration separate from the application code (the first case above). Once you are happy with it, you can keep it directly with your configuration. The first option also lets hints be provided for code you do not know or otherwise have access to change or rebuild.

These hints are in addition to any .json files existing anywhere in the classpath that native-image will pick up, you don’t have to choose one format or the other.

8.4. Triggered

We mentioned a trigger may be a piece of Spring infrastructure or just a regular class - but that it was also optional, what if there is no trigger? There are two possibilities:

  • If the hint is on a NativeImageConfiguration class then it is assumed this configuration should always apply. This is useful for common configuration necessary for all applications.

  • If the hint is on something other than a NativeImageConfiguration class (e.g. on a Spring auto-configuration class) then that type is considered to be the trigger, and it the feature determines that is 'active', the hint apply.

8.5. What do hints look like?

The following listing shows a hint:

@NativeImageHint(trigger = JacksonAutoConfiguration.class,
  proxyInfos = {
    @ProxyInfo(types={Serializable.class,SomeOtherInterface.class})
  },
  typeInfos = {
    @TypeInfo(types = { JsonGenerator.class },
    access = AccessBits.CLASS | AccessBits.PUBLIC_METHODS
			 | AccessBits.PUBLIC_CONSTRUCTORS)
  })
  • the trigger is JacksonAutoConfiguration - if that configuration looks to be active in the application (which usually means: do the conditional on class checks on JacksonAutoConfiguration pass?) then the associated configuration will be used.

  • Within the NativeImageHint it is possible to include everything you can specify in the JSON files. Proxies, resources, reflection (down to the individual member), even initialization. Here, if the hint is 'active', it specifies we want a JDK proxy in the native image for the pair of classes Serializable and SomeOtherInterface. It also specifies reflective access is required to JsonGenerator - reflective access to the methods and constructors is necessary at runtime (not the fields).

To fully complete the picture, here is the full list of what can be specified in a hint:

  • proxyInfos which list proxies for which types are needed and should be built into the image.

  • typeInfos which lists any reflective needs. It should use class references but string names for classes are allowed if visibility (private classes) prevents a class reference.

  • resourcesInfos which lists patterns that match resources (including .class files) that should be included in the image.

  • initializationInfos which lists classes/packages that should be explicitly initialized at either build-time or run-time. There should not really be a trigger specified on hints included initializationInfos.

  • importInfos can be useful if two hints share a number of typeInfos/proxyInfos/etc in common. For example reactive-web and webmvc may expose a lot of common infrastructure. Rather than duplicate those in two places, those info annotations (TypeInfo/ProxyInfo/etc) can all be placed on a separate type and then importInfos can reference that type to pull them into a particular NativeImageHint.

  • applyToFunctional if specified indicates whether this hint should apply when the feature is operating in functional mode. The operating mode is set via the spring.native.mode property (mode being one of REFLECTION/INIT/AGENT/FUNCTIONAL).

8.6. Codifying hint computation/validation

The NativeImageConfiguration interface contains a couple of default methods that can be implemented for more control.

For example whether the hints on a NativeImageConfiguration should activate may be a more subtle condition that simply whether a configuration is active. It is possible to implement the isValid method in a NativeImageConfiguration implementation and perform a more detailed test, returning false from this method will deactivate the associated hints.

Additionally sometimes the necessary configuration is hard to statically declare and needs a more dynamic approach. For example the interfaces involved in a proxy hint might need something to be checked beyond the simple presence of a class. In this case the method computeHints can be implemented which allows computation of hints in a more dynamic way, which are then combined with those statically declared via annotations.

8.7. Structure of the spring-graalvm-native-configuration module

In the spring-graalvm-native-configuration, numerous package names look like Spring package names. That is deliberate. Notice the use of direct class references in the hints rather than strings. This type safety is a little more robust. If we upgrade a Spring version and the configuration module no longer compiles, we know something has changed that needs to be addressed. We may not have noticed if we use only string references. The reason these package names match Spring package names is visibility. With this setup, the hint can refer to a type with only package visibility in the original code. What about private classes? There is a fallback, in that @TypeInfo can specify names as strings if it absolutely must, as follows:

@TypeInfo(
  typeNames="com.foo.PrivateBar",
  types= {PublicBar.class}
)

Notice no access is specified here. There is a default access of allowing the loading and construction of a type (so its constructors are exposed), by default methods and fields are not. There are cases where this default may be overridden by the feature if it knows more access is needed from looking at the characteristics of the type mentioned.

8.8. Contributing new hints

The typical approach is:

  1. Notice an error if your application when you try to build it or run it — a classnotfound, methodnotfound, or similar error. If you are using a piece of Spring we don’t have a sample for, this is likely to happen.

  2. Try to determine which configuration classes give rise to the need for that reflective access to occur. Usually, we do a few searches for references to the type that is missing, and those searches guide us to the configuration.

  3. If there is already a NativeImageConfiguration implementation for that configuration, augment it with the extra type info. If there is not, create one, attach a @NativeImageHint to it to identify the triggering configuration and the classes that need to be exposed, and add it to the META-INF/services/org.springframework.nativex.extension.NativeImageConfiguration . You may also need to set the accessibility in the annotation (in the @TypeInfo). It is possible that more dependencies may need to be added to the configuration project to allow the direct class references. That is OK, so long as you ensure that they are provided scope.

8.9. Taking more control via processors

Within a Spring application there are going to be a number of active components (the main application, configurations, controllers, etc). There may be much more sophisticated analysis to be done for these components in order to compute the necessary configuration for the native-image invocation. It is possible to implement a couple of interfaces to participate in the process the feature is going through:

  • ComponentProcessor implementations are given the chance to process components and possibly register new configuration. For example this is used by spring-data (via SpringDataComponentProcessor) to do deeper analysis of repositories and the types used in generic signatures to compute reflection/proxy/resource hints.

  • SpringFactoriesProcessor implementations are given a chance to process the keys and values loaded from spring.factories files. Currently they are allowed to do filtering but this is likely to be expanded in the future. By filtering it means they may programmatically compute that for some spring.factories key one of the values makes no sense (by analysing classpath contents, for example). In this case they can filter out that value and the spring.factories subsequently added to the native-image will have it removed.

8.10. Is this the way forward?

As we play around with this extension mechanism to determine suitability, we are thinking through a number of pros and cons:

Pros:

  • The type safety of using direct class references is nice. Grouping types and knowing the trigger that causes their need to be accessible makes the system easier to reason about and debug.

  • When looking at one entry in a giant json file, you may have no idea why that is needed. With the hint structure, you can know exactly which configuration causes it to be needed.

Cons:

  • Not being able to use direct class references for everything is not ideal. (For example private classes).

  • It looks like split packages in some cases, which is not nice. This is because the visibility of the type needing to be specified is default (package) visibility.

The project is still incubating, so be aware these mechanisms could still evolve significantly.

9. Contact us

We would love to hear about your successes and failures (with minimal repro projects) through the project issue tracker. Before raising an issue, please check the troubleshooting guide, which is full of information on pitfalls, common problems, and how to deal with them (through fixes and workarounds).

If you want to make a contribution here, see the how to contribute guide. Please be aware this project is still incubating and, as such, some of these options and extension APIs are still evolving and may change before it is finally considered done.