Spring has released a new tool, Spring Native Beta, to convert existing Spring Boot applications, written in Java or Kotlin, to GraalVM native images. The goal is to support Spring Boot applications on Spring Native. GraalVM native images are small, optimized and boot quickly. The tradeoffs, however, are longer build times and fewer runtime optimizations compared to the JVM.
Working with the GraalVM team for the past 18 months, Spring Native acts as a bridge to ensure GraalVM understands traditional Spring Boot code. Vojin Jovanovic, senior research manager at Oracle Labs, speaking on their collaboration with Spring, stated:
It is a great joy to collaborate with the Spring team on crafting the native JVM ecosystem: their deep technical knowledge, wrapped with sensitive touch for the community always leads to the best solutions. The latest Spring Native release, and its numerous usages in the JVM ecosystem, pave the way for the wide adoption of native compilation.
Under the hood, ahead-of-time (AOT) plugins, provided by Maven and Gradle, are used to transform a Spring Boot application to native code. These plugins generate GraalVM native configuration for all Spring elements which are not supported by GraalVM such as reflection and proxies. The resulting configuration is stored in the reflect-config.json
file. For example, each class annotated with @Service
is configured in the JSON file.
Spring Native configuration files are discoverable assuming they reside in the META-INF/native-image
folder. Native hints are available to configure elements Spring Native doesn’t support (yet), such as the MySQL driver configuration.
The Spring Native dependency is available in Spring Initializr when creating a new project.
Some dependencies, such as Spring Data JPA, require extra configuration to support GraalVM. The Spring team provides these extra configurations through plugins. After the Spring Data JPA dependency is added in Intitializr, for example, two extra Maven plugins, spring-aot-maven-plugin
and hibernate-enhance-maven-plugin,
including their configuration are added to the pom.xml
file.
Not all dependencies are supported by Spring Native at this time. If an unsupported dependency is included in the Spring project generated by Intializr, the HELP.md
file will list it.
Like any other Spring Boot application, Initializr automatically creates the appropriate entry point class to run the application:
@SpringBootApplication
public class NativeApplication {
public static void main(String[] args) {
SpringApplication.run(NativeApplication.class, args);
}
}
Next, provide a REST controller to test the application:
@Controller
public class NativeEndpoint {
@GetMapping("/native")
public String nativeCall() {
return "Native";
}
}
Except for the standard Spring Boot dependencies, one new dependency was added by Initializr for Spring Native support:
<dependency>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-native</artifactId>
<version>${spring-native.version}</version>
</dependency>
The spring-boot-maven-plugin
received some extra configuration to generate the native image:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<builder>paketobuildpacks/builder:tiny</builder>
<env>
<BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
</env>
</image>
</configuration>
</plugin>
Maven may be used to generate the native image with ahead-of time compilation. The resulting executable includes all necessary classes and statically linked native code from the JDK. Substrate VM is used as the runtime system instead of the ‘normal ‘Java VM.
$ ./mvnw spring-boot:build-image
Building the image takes some time while the code is compiled and optimized. This step is executed in a Docker image and requires quite some memory. The memory settings for Docker should be increased should the following error occur:
Exception in thread "native-image pid watcher" java.lang.OutOfMemoryError:
GC overhead limit exceeded
Output from executing the Maven command above shows the resulting Docker image which contains the GraalVM native image:
Successfully built image 'docker.io/library/native:0.0.1-SNAPSHOT'
The resulting image is about 82 MB and contains everything to run the application:
$ docker images
native 0.0.1-SNAPSHOT ... 81.9MB
The Docker container is started on port 8080:
$ docker run -p 8080:8080 native:0.0.1-SNAPSHOT
After starting the container, the application is almost instantly available for testing:
$ curl localhost:8080/native --silent
Native
More details may be found in the Spring Native documentation including an alternative to installing GraalVM with Docker.
The Spring team has provided numerous examples on how to use Spring Native in conjunction with other Spring projects. QCon and SpringOne presentations with more in-depth information about the performance of GraalVM and the integration of Spring Boot with GraalVM native images, respectively, are available as well.