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 andspring.factories
functional alternative (only Spring Bootspring.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
-
You need to configure Hibernate build-time bytecode enhancement (Maven example)
-
src/main/resources/hibernate.properties
should definehibernate.bytecode.provider=none
(example) -
--enable-all-security-services
and-H:+AddAllCharsets
may be required depending on your JDBC driver.
-
-
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 oftomcat-embed-core
andtomcat-embed-websocket
ones for optimized footprint. If you are usingspring-boot-starter-web
and Maven, you should use the followingpom.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.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:
-
Using Spring Boot Maven or Gradle Buildpacks support to generate a lightweight container containing a native executable
-
Using GraalVM native image Maven plugin support to generate a native executable
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. |
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
plugins {
id 'org.springframework.boot' version '2.4.1'
}
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.
<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>
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"
]
}
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:
<repositories>
<repository>
<id>spring-milestone</id>
<name>Spring milestone</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
repositories {
maven { url 'https://repo.spring.io/milestone' }
}
repositories {
maven { url = uri("https://repo.spring.io/milestone") }
}
And for plugins:
<pluginRepositories>
<pluginRepository>
<id>spring-milestone</id>
<name>Spring milestone</name>
<url>https://repo.spring.io/milestone</url>
</pluginRepository>
</pluginRepositories>
pluginManagement {
repositories {
maven { url 'https://repo.spring.io/milestone' }
}
}
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:
<dependencies>
<dependency>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-graalvm-native</artifactId>
<version>0.8.5</version>
</dependency>
</dependencies>
dependencies {
implementation 'org.springframework.experimental:spring-graalvm-native:0.8.5'
}
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:
mvn spring-boot:build-image
gradle bootBuildImage
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:
Or you can use SDKMAN to easily switch between GraalVM versions:
-
Install GraalVM with
sdk install java 20.3.0.r8-grl
for Java 8 orsdk 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.
5.1.2. Options recommended by default
-
--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 andspring.factories
functional alternative (only Spring Bootspring.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 ofdefault-include-all
ordefault-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 specifyingmatchIfMissing=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).
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 theaccess-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.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 thespring-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 areflect-config.json
under aMETA-INF/native-image
folder structure and ensure that is on the classpath. This is sometimes most easily accomplished by creating it in your projectsrc/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 setallDeclaredConstructors
andallDeclaredMethods
. We might needallDeclaredFields
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 viadocker/build-graalvm-ce-images.sh
. -
spring-graalvm-native
: base image withgraalvm-ce
+ utilities required to build the project, available from Docker hub or locally viadocker/build-spring-graalvm-native-images.sh
. -
spring-graalvm-native-dev
: local image built viadocker/build-dev-images.sh
designed to share the same user between the host and the container
To use it:
-
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 runspring-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 onJacksonAutoConfiguration
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 classesSerializable
andSomeOtherInterface
. It also specifies reflective access is required toJsonGenerator
- 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 includedinitializationInfos
. -
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 thenimportInfos
can reference that type to pull them into a particularNativeImageHint
. -
applyToFunctional
if specified indicates whether this hint should apply when the feature is operating in functional mode. The operating mode is set via thespring.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:
-
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. -
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.
-
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 theMETA-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 (viaSpringDataComponentProcessor
) 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 fromspring.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 thespring.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.