This example shows how to build a simple Spring REST application using the GraalVM Native Image Layers feature.
- Linux x64
- Latest GraalVM 25.1 EA build (with Native Image support)
Native Image Layers is an experimental feature. For the best experience use the latest GraalVM Early Access Build.
Point your JAVA_HOME to the GraalVM distribution.
export JAVA_HOME=/path/to/graalvm/ea/buildStart by generating a basic application using the online generator. For more details, see the Spring guide.
On the web page, choose Maven for the Project. Then choose Java for Language.
Then choose any version, and any name for the project.
Finally, use Jar for Packaging.
This creates a new Spring project with the following structure:
demo/
├── pom.xml
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── example/demo/DemoApplication.java
│ │ └── resources/
│ │ └── application.properties
│ └── test/
└── mvnw (Maven wrapper)
For executing the subsequent commands, enter the project directory:
cd spring-hello-rest-maven-layeredAdd a custom controller to src/main/java/com/example/demo/HelloController.java:
package com.example.demo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/")
public String index() {
return "Greetings from Spring Boot!";
}
}First, build a standalone executable for this simple application.
For this, extend the pom.xml with a custom profile and configure the native build using the GraalVM Native Image Maven plugin:
<profile>
<id>standalone</id>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>${native.maven.plugin.version}</version>
<configuration>
<imageName>spring-demo</imageName>
<mainClass>com.example.demo.DemoApplication</mainClass>
<buildArgs combine.children="append">
<buildArg>--verbose</buildArg>
</buildArgs>
</configuration>
</plugin>
</plugins>
</build>
</profile>Using this profile you can now generate the executable:
cd spring-demo
../mvnw clean package -Pnative -PstandaloneThis will generate an executable file that you can run:
./target/spring-demo
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v4.0.2)
...
2026-01-29T17:08:02.666+01:00 INFO 160744 --- [demo] [ main] com.example.demo.DemoApplication : Started DemoApplication in 0.029 seconds (process running for 0.032)Test your custom endpoint:
curl localhost:8080The expected output is:
Greetings from Spring Boot!
Next, create a base layer that contains java.base.
For this, use the following build in the pom.xml file:
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>${native.maven.plugin.version}</version>
<executions>
<execution>
<goals>
<goal>compile-no-fork</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
<configuration>
<imageName>libjavabaselayer</imageName>
<classesDirectory>null</classesDirectory>
<buildArgs>
<buildArg>-H:+UnlockExperimentalVMOptions</buildArg>
<buildArg>-H:LayerCreate=base-layer.nil,digest-ignore,module=java.base,module=java.desktop,module=java.net.http,module=java.management,module=java.sql,module=jdk.unsupported</buildArg>
<buildArg>-H:-UnlockExperimentalVMOptions</buildArg>
</buildArgs>
</configuration>
</plugin>
</plugins>
</build>
The -H:LayerCreate= option is used to specify what should be included in the base layer: java.base and a few more other packages that a Spring application usually depends on.
For more details, consult the Native Image Layers documentation.
Now you can build the base layer:
../mvnw clean installThis will create the base-layer.nil which is a build time dependency for the application build.
It will also create the libjavabaselayer.so shared library which is a run time dependency for the application layer.
Note also that you use install instead of package to ensure that the base layer JAR is installed in the .m2 cache as it will be needed by the application build later.
The <classesDirectory>null</classesDirectory> configuration excludes the main JAR from the image by specifying a directory that does not exist.
To configure the application layer, add an additional profile:
<profile>
<id>app-layer</id>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>${native.maven.plugin.version}</version>
<configuration>
<imageName>spring-demo-layered</imageName>
<mainClass>com.example.demo.DemoApplication</mainClass>
<buildArgs combine.children="append">
<buildArg>--verbose</buildArg>
<buildArg>-H:+UnlockExperimentalVMOptions</buildArg>
<buildArg>-H:LayerUse=../base-layer-test/target/base-layer.nil</buildArg>
<buildArg>-H:LinkerRPath=$ORIGIN</buildArg>
<buildArg>-H:-UnlockExperimentalVMOptions</buildArg>
</buildArgs>
</configuration>
</plugin>
</plugins>
</build>
</profile>Now you can build a layered native image which depends on the base layer that you created earlier:
../mvnw clean package -Dpackaging=native-image -Pnative -Papp-layerThis will generate the layered executable file in ./target/spring-demo-layered and will copy libjavabaselayer.so next to it.
Then you can execute the layered application:
./target/spring-demo-layered
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v4.0.2)
...
2026-01-29T17:01:12.362+01:00 INFO 158071 --- [demo] [ main] com.example.demo.DemoApplication : Started DemoApplication in 0.031 seconds (process running for 0.035)Test it with:
curl localhost:8080
The expected output is:
Greetings from Spring Boot!