Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: microservices/micronaut-hello-rest-maven-layered
on:
push:
paths:
- 'native-image/microservices/micronaut-hello-rest-maven-layered/**'
- '.github/workflows/microservices-micronaut-hello-rest-maven-layered.yml'
schedule:
- cron: "0 0 1 * *" # run every month
workflow_dispatch:
permissions:
contents: read
jobs:
run:
name: Run 'microservices/micronaut-hello-rest-maven-layered'
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
matrix:
java-version: ['25']
steps:
- uses: actions/checkout@v4
- uses: graalvm/setup-graalvm@v1
with:
java-version: ${{ matrix.java-version }}
distribution: 'graalvm'
github-token: ${{ secrets.GITHUB_TOKEN }}
cache: 'maven'
native-image-job-reports: 'true'
- name: Run 'microservices/micronaut-hello-rest-maven-layered'
run: |
cd native-image/microservices/micronaut-hello-rest-maven-layered
./run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Thumbs.db
.DS_Store
.gradle
build/
target/
out/
.micronaut/
.idea
*.iml
*.ipr
*.iws
.project
.settings
.classpath
.factorypath
app-layer-target
base-layer-target
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.10/apache-maven-3.9.10-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
# Microanut Layered Native Image Demo

This example shows how to build a simple [Micronaut](https://micronaut.io/) REST application using the [GraalVM Native Image Layers](https://github.com/oracle/graal/blob/master/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/NativeImageLayers.md) feature.

## Environment Setup
Point your `JAVA_HOME` to a GraalVM distribution.
Native Image Layers is an experimental feature, for best experience use the latest [GraalVM Early Access Build](https://github.com/graalvm/oracle-graalvm-ea-builds/releases).
```bash
export JAVA_HOME=/path/to/graalvm/ea/build
```

## Create The Micronaut Application

We'll start by generating a basic application using the Micronaut CLI.
For more details see the [Micronaut guide](https://guides.micronaut.io/latest/creating-your-first-micronaut-app-maven-java.html).

First we need to install the `mn` tool:
```bash
sdk install micronaut 4.9.4
sdk use micronaut 4.9.4
```

Now we ca generate the basic app:
```bash
mn create-app example.micronaut.micronaut-hello-rest-maven-layered --build=maven --lang=java --features=graalvm
```

### Add A Custom Controller
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
### Add A Custom Controller
### Add a Custom Controller


We'll add a custom controller to `src/main/java/example/micronaut/HelloController.java`:
```java
package example.micronaut;

import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Produces;

@Controller("/hello")
public class HelloController {
@Get
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "Hello from GraalVM Native Image!";
}
}
```

### Standalone Application

We'll first demonstrate how to build a standalone executable for this simple app.
For this we'll extend the `pom.xml` with a custom profile and configure the native build using the [GraalVM Native Image Maven plugin](https://graalvm.github.io/native-build-tools/latest/maven-plugin.html):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
For this we'll extend the `pom.xml` with a custom profile and configure the native build using the [GraalVM Native Image Maven plugin](https://graalvm.github.io/native-build-tools/latest/maven-plugin.html):
For this, we'll extend the `pom.xml` with a custom profile and configure the native build using the [GraalVM Native Image Maven plugin](https://graalvm.github.io/native-build-tools/latest/maven-plugin.html):

```xml
<profile>
<id>standalone</id>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>0.10.3</version>
<configuration>
<imageName>standalone-app</imageName>
<mainClass>example.micronaut.Application</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</profile>
```

Using this profile we can now generate the executable:
```bash
./mvnw clean package -Dpackaging=native-image -Pstandalone
```

This will generate an executable file that we can run
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This will generate an executable file that we can run
This will generate an executable file that we can run:

```bash
./target/standalone-app
__ __ _ _
| \/ (_) ___ _ __ ___ _ __ __ _ _ _| |_
| |\/| | |/ __| '__/ _ \| '_ \ / _` | | | | __|
| | | | | (__| | | (_) | | | | (_| | |_| | |_
|_| |_|_|\___|_| \___/|_| |_|\__,_|\__,_|\__|
12:20:53.437 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 6ms. Server Running: http://localhost:8080
```
and test our custom endpoint
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
and test our custom endpoint
and test our custom endpoint:

```bash
curl localhost:8080/hello
Hello from GraalVM Native Image!
```

### Layered Application

#### Configure The Base Layer

We will create a base layer that contains both `java.base` and the Micronaut framework.
For this we'll add a second custom profile:
```xml
<profile>
<id>base-layer</id>
<build>
<directory>${project.basedir}/base-layer-target</directory>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>0.10.3</version>
<configuration>
<imageName>libmicronautbaselayer</imageName>
<mainClass>.</mainClass>
<buildArgs>
<buildArg>-H:+UnlockExperimentalVMOptions</buildArg>
<buildArg>-H:LayerCreate=base-layer.nil,module=java.base,package=io.micronaut.*,package=io.netty.*,package=jakarta.*,package=com.fasterxml.jackson.*,package=org.slf4j.*,package=reactor.*,package=org.reactivestreams.*</buildArg>
<buildArg>-H:ApplicationLayerOnlySingletons=io.micronaut.core.io.service.ServiceScanner$StaticServiceDefinitions</buildArg>
<buildArg>-H:ApplicationLayerInitializedClasses=io.micronaut.inject.annotation.AnnotationMetadataSupport</buildArg>
<buildArg>-H:ApplicationLayerInitializedClasses=io.micronaut.core.io.service.MicronautMetaServiceLoaderUtils</buildArg>
<buildArg>-H:-UnlockExperimentalVMOptions</buildArg>
</buildArgs>
</configuration>
</plugin>
</plugins>
</build>
</profile>
```

We use `-H:LayerCreate=` to specify what should be included in the base layer: `java.base`, `io.micronaut`, `io.netty` and a few more other packages that a Micronaut application usually depends on.
For more details consult the [Native Image Layers documentation](https://github.com/oracle/graal/blob/master/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/NativeImageLayers.md).

Additionally we use two options that are specific for layered builds: `-H:ApplicationLayerOnlySingletons=`, which specifies that a singleton object should be installed in the application layer only, and `-H:ApplicationLayerInitializedClasses=`, which registers a class as being initialized in the app layer.
These are necessary for correctly building the Micronaut framework in a layered set-up.

Now we can build the base layer:
```bash
./mvnw clean install -Dpackaging=native-image -Pbase-layer
```
This will create the `base-layer.nil` which is a build time dependency for the application build.
It will also create the `libmicronautbaselayer.so` shared library which is a run time dependency for the application layer.
Note also that we 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.


### Configure The Application Layer

To configure the app layer we'll add an additional profile:
```xml
<profile>
<id>app-layer</id>
<build>
<directory>${project.basedir}/app-layer-target</directory>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>0.10.3</version>
<configuration>
<imageName>layered-app</imageName>
<mainClass>example.micronaut.Application</mainClass>
<buildArgs>
<buildArg>-H:+UnlockExperimentalVMOptions</buildArg>
<buildArg>-H:LayerUse=base-layer-target/base-layer.nil</buildArg>
<buildArg>-H:LinkerRPath=$ORIGIN</buildArg>
<buildArg>-H:-UnlockExperimentalVMOptions</buildArg>
</buildArgs>
</configuration>
</plugin>
</plugins>
</build>
</profile>
```

Now we can build a layered Native Image which depends on the base layer that we created earlier:
```bash
./mvnw clean package -Dpackaging=native-image -Papp-layer
```

This will generate the layered executable file in `./app-layer-target/layered-app` and will copy `libmicronautbaselayer.so` next to it.

Then we can execute the layered application:
```bash
./app-layer-target/layered-app
__ __ _ _
| \/ (_) ___ _ __ ___ _ __ __ _ _ _| |_
| |\/| | |/ __| '__/ _ \| '_ \ / _` | | | | __|
| | | | | (__| | | (_) | | | | (_| | |_| | |_
|_| |_|_|\___|_| \___/|_| |_|\__,_|\__,_|\__|
12:24:21.341 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 6ms. Server Running: http://localhost:8080
```
and test it with:
```
curl localhost:8080/hello
Hello from GraalVM Native Image!
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# AOT configuration properties for jar packaging
# Please review carefully the optimizations enabled below
# Check https://micronaut-projects.github.io/micronaut-aot/latest/guide/ for more details

# Caches environment property values: environment properties will be deemed immutable after application startup.
cached.environment.enabled=true

# Precomputes Micronaut configuration property keys from the current environment variables
precompute.environment.properties.enabled=true

# Replaces logback.xml with a pure Java configuration
logback.xml.to.java.enabled=true

# Converts YAML configuration files to Java configuration
yaml.to.java.config.enabled=true

# Scans for service types ahead-of-time, avoiding classpath scanning at startup
serviceloading.jit.enabled=true

# Scans reactive types at build time instead of runtime
scan.reactive.types.enabled=true

# Deduces the environment at build time instead of runtime
deduce.environment.enabled=true

# Checks for the existence of some types at build time instead of runtime
known.missing.types.enabled=true

# Precomputes property sources at build time
sealed.property.source.enabled=true

# The list of service types to be scanned (comma separated)
service.types=io.micronaut.context.env.PropertySourceLoader,io.micronaut.inject.BeanConfiguration,io.micronaut.inject.BeanDefinitionReference,io.micronaut.http.HttpRequestFactory,io.micronaut.http.HttpResponseFactory,io.micronaut.core.beans.BeanIntrospectionReference,io.micronaut.core.convert.TypeConverterRegistrar,io.micronaut.context.env.PropertyExpressionResolver

# A list of types that the AOT analyzer needs to check for existence (comma separated)
known.missing.types.list=io.reactivex.Observable,reactor.core.publisher.Flux,kotlinx.coroutines.flow.Flow,io.reactivex.rxjava3.core.Flowable,io.reactivex.rxjava3.core.Observable,io.reactivex.Single,reactor.core.publisher.Mono,io.reactivex.Maybe,io.reactivex.rxjava3.core.Single,io.reactivex.rxjava3.core.Maybe,io.reactivex.Completable,io.reactivex.rxjava3.core.Completable,io.methvin.watchservice.MacOSXListeningWatchService,io.micronaut.core.async.publisher.CompletableFuturePublisher,io.micronaut.core.async.publisher.Publishers.JustPublisher,io.micronaut.core.async.subscriber.Completable

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# AOT configuration properties for native-image packaging
# Please review carefully the optimizations enabled below
# Check https://micronaut-projects.github.io/micronaut-aot/latest/guide/ for more details

# Caches environment property values: environment properties will be deemed immutable after application startup.
cached.environment.enabled=true

# Precomputes Micronaut configuration property keys from the current environment variables
precompute.environment.properties.enabled=true

# Replaces logback.xml with a pure Java configuration
logback.xml.to.java.enabled=true

# Converts YAML configuration files to Java configuration
yaml.to.java.config.enabled=false

# Generates GraalVM configuration files required to load the AOT optimizations
graalvm.config.enabled=true

# Scans for service types ahead-of-time, avoiding classpath scanning at startup
serviceloading.native.enabled=false

# Scans reactive types at build time instead of runtime
scan.reactive.types.enabled=true

# Deduces the environment at build time instead of runtime
deduce.environment.enabled=true

# Checks for the existence of some types at build time instead of runtime
known.missing.types.enabled=true

# Precomputes property sources at build time
sealed.property.source.enabled=true

# The list of service types to be scanned (comma separated)
service.types=io.micronaut.context.env.PropertySourceLoader,io.micronaut.inject.BeanConfiguration,io.micronaut.inject.BeanDefinitionReference,io.micronaut.http.HttpRequestFactory,io.micronaut.http.HttpResponseFactory,io.micronaut.core.beans.BeanIntrospectionReference,io.micronaut.core.convert.TypeConverterRegistrar,io.micronaut.context.env.PropertyExpressionResolver

# A list of types that the AOT analyzer needs to check for existence (comma separated)
known.missing.types.list=io.reactivex.Observable,reactor.core.publisher.Flux,kotlinx.coroutines.flow.Flow,io.reactivex.rxjava3.core.Flowable,io.reactivex.rxjava3.core.Observable,io.reactivex.Single,reactor.core.publisher.Mono,io.reactivex.Maybe,io.reactivex.rxjava3.core.Single,io.reactivex.rxjava3.core.Maybe,io.reactivex.Completable,io.reactivex.rxjava3.core.Completable,io.methvin.watchservice.MacOSXListeningWatchService,io.micronaut.core.async.publisher.CompletableFuturePublisher,io.micronaut.core.async.publisher.Publishers.JustPublisher,io.micronaut.core.async.subscriber.Completable

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
applicationType: default
defaultPackage: example.micronaut
testFramework: junit
sourceLanguage: java
buildTool: maven
features: [app-name, graalvm, http-client-test, java, java-application, junit, logback, maven, maven-enforcer-plugin, micronaut-aot, micronaut-http-validation, netty-server, properties, readme, serialization-jackson, shade, static-resources]
Loading