[Java - Spring Native] Use Gradle to compile Spring Boot Web app with Spring Native and GraalVM Java

Gradle, Spring Native and GraalVM Java

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

Compared to the Java Virtual Machine, native images can enable cheaper and more sustainable hosting for many types of workloads. These include microservices, function workloads, well suited to containers, and Kubernetes

Gradle is an open-source build automation tool focused on flexibility and performance. Gradle build scripts are written using a Groovy - https://groovy-lang.org/ or Kotlin - https://kotlinlang.org/ DSL. Read about Gradle features - https://gradle.org/features/ to learn what is possible with Gradle.

Sample Project Setup

The completed “RESTful Web Service” guide can be retrieved using the following commands:

1
2
3
$ git clone https://github.com/spring-guides/gs-rest-service

$ cd gs-rest-service/complete

Validate Spring Boot version

Spring Native 0.11.1 only supports Spring Boot 2.6.2, so change the version if necessary.

1
2
3
4
5
6
// build.gradle

plugins {
// ...
id 'org.springframework.boot' version '2.6.2'
}

Add the Spring AOT plugin

The Spring AOT plugin performs ahead-of-time transformations required to improve native image compatibility and footprint.

1
2
3
4
5
6
// build.gradle

plugins {
// ...
id 'org.springframework.experimental.aot' version '0.11.1'
}

The plugin provides a number of options to customize the transformations, see spring-aot-configuration - https://docs.spring.io/spring-native/docs/current/reference/htmlsingle/#spring-aot-configuration for more details.

Enable native image support

Spring Boot’s Cloud Native Buildpacks - https://docs.spring.io/spring-boot/docs/2.6.2/reference/html/spring-boot-features.html#boot-features-container-images-buildpacks support lets you build a container for your Spring Boot application. The native image buildpack - https://github.com/paketo-buildpacks/native-image can be enabled using the BP_NATIVE_IMAGE environment variable as follows:

1
2
3
4
5
6
7
8
// build.gradle

bootBuildImage {
builder = "paketobuildpacks/builder:tiny"
environment = [
"BP_NATIVE_IMAGE" : "true"
]
}

tiny builder allows small footprint and reduced surface attack, you can also use base (the default) or full builders to have more tools available in the image for an improved developer experience.
Additional native-image arguments can be added using the BP_NATIVE_IMAGE_BUILD_ARGUMENTS environment variable.


Freeze GraalVM version

By default, GraalVM versions will be upgraded automatically by Buildpacks to the latest release. You can explicitly configure Spring Boot Maven or Gradle plugins with a specific version of java-native-image buildpack which will freeze GraalVM version, see related versions mapping. For example, if you want to force using GraalVM 21.3.0, you can configure:

1
2
3
4
5
6
// build.gradle

bootBuildImage {
// ...
buildpacks = ["gcr.io/paketo-buildpacks/java-native-image:7.1.0"]
}

Maven Repository

Configure your build to include the required repository for the spring-native dependency, as follows:

1
2
3
4
5
6
// build.gradle

repositories {
// ...
maven { url 'https://repo.spring.io/release' }
}

The Spring AOT plugin also requires a dedicated plugin repository in the pom.xml file for Maven and in the in the settings.gradle(.kts) for Gradle.

1
2
3
4
5
6
7
8
// build.gradle

pluginManagement {
repositories {
// ...
maven { url 'https://repo.spring.io/release' }
}
}

Build the native application


On MacOS, it is recommended to increase the memory allocated to Docker to at least 8GB with allocation to more CPU core. On Microsoft Windows make sure to enable the Docker WSL 2 backend for better performance.

See How to assign more memory to docker container - Stack Overflow - https://stackoverflow.com/questions/44533319/how-to-assign-more-memory-to-docker-container/44533437#44533437 to learn more.


The native application can be built as follows:

1
$ gradle nativeCompile

This creates a executable file rest-service-complete in build/native/nativeCompile/ directory.

Or

1
$ gradle bootBuildImage

This creates a Linux container to build the native application using the GraalVM native image compiler. By default, the container image is installed locally.

Run the native application

To run the application

1
$ build/native/nativeCompile/rest-service-complete

Or you can use docker the usual way as shown in the following example:

1
$ docker run --rm -p 8080:8080 rest-service-complete:0.0.1-SNAPSHOT

If you prefer docker-compose, you can write a docker-compose.yml at the root of the project with the following content:

1
2
3
4
5
6
version: '3.1'
services:
rest-service:
image: rest-service-complete:0.0.1-SNAPSHOT
ports:
- "8080:8080"

And then run

1
$ docker-compose up

The startup time should be less than 100ms, compared to the roughly 1500ms when starting the application on the JVM.

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

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

FAQs

‘gu’ tool wasn’t found. This probably means that JDK at isn’t a GraalVM distribution.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ gradle nativeCompile
> Task :generateResourcesConfigFile
[native-image-plugin] Resources configuration written into /Users/blogbin/WorkSpaces/Benjamin/CloudoLife/col-services/col-common-configuration-service/col-common-configuration-service-java/build/native/generated/generateResourcesConfigFile/resource-config.json

> Task :nativeCompile FAILED
Toolchain detection is disabled, will use GraalVM from /usr/local/opt/openjdk/libexec/openjdk.jdk/Contents/Home.

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':nativeCompile'.
> Determining GraalVM installation failed with message: 'gu' tool wasn't found. This probably means that JDK at isn't a GraalVM distribution.

Make sure to declare the GRAALVM_HOME environment variable or install GraalVM with native-image in a standard location recognized by Gradle Java toolchain support

Specify JAVA_HOME env to GraalVM directory before run gradle nativeCompile.

1
2
3
$ export JAVA_HOME=/Users/cloudolife/.asdf/installs/java/graalvm-21.3.0+java17

$ gradle nativeCompile

See java - How do I tell Gradle to use specific JDK version? - Stack Overflow - https://stackoverflow.com/questions/18487406/how-do-i-tell-gradle-to-use-specific-jdk-version

Native reflection configuration for org.hibernate.dialect.PostgreSQLDialect is missing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
org.hibernate.boot.registry.selector.spi.StrategySelectionException: Unable to resolve name [org.hibernate.dialect.PostgreSQLDialect] as strategy [org.hibernate.dialect.Dialect]

...

java.lang.ClassNotFoundException: org.hibernate.dialect.PostgreSQLDialect

***************************
APPLICATION FAILED TO START
***************************

Description:

Native reflection configuration for org.hibernate.dialect.PostgreSQLDialect is missing.

Action:

Native configuration for a class accessed reflectively is likely missing.
You can try to configure native hints in order to specify it explicitly.
See https://docs.spring.io/spring-native/docs/current/reference/htmlsingle/#native-hints for more details.

TODO

References

[1] GraalVM - https://www.graalvm.org/

[2] Spring Native documentation - https://docs.spring.io/spring-native/docs/current/reference/htmlsingle

[3] Spring Native: Getting started with GraalVM native images - https://www.thomasvitale.com/spring-native-graalvm-getting-started/

[4] spring-native-graalvm-demo/build.gradle at main · ThomasVitale/spring-native-graalvm-demo - https://github.com/ThomasVitale/spring-native-graalvm-demo/blob/main/build.gradle

[5] The Apache Groovy programming language - https://groovy-lang.org/

[6] Kotlin Programming Language - https://kotlinlang.org/

[7] Gradle | Gradle Features - https://gradle.org/features/

[8] Get Started with Docker | Docker - https://www.docker.com/get-started

[9] Overview of Docker Compose | Docker Documentation - https://docs.docker.com/compose/

[10] Reflection Use in Native Images | GraalVM - https://www.graalvm.org/reference-manual/native-image/Reflection/