Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 

README.md

Spring Layered Native Image Demo

This example shows how to build a simple Spring REST application using the GraalVM Native Image Layers feature.

Prerequisites

  • 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.

Environment Setup

Point your JAVA_HOME to the GraalVM distribution.

export JAVA_HOME=/path/to/graalvm/ea/build

Create the Spring Application

Start 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-layered

Add a Custom Controller

Add 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!";
	}

}

Standalone Application

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 -Pstandalone

This 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:8080

The expected output is:

Greetings from Spring Boot!

Layered Application

Configure the Base Layer

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 install

This 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.

Configure The Application Layer

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-layer

This 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!

Learn More