13.4.1 Microservices as GraalVM native images

Getting Started with Micronaut and GraalVM

Since Micronaut 2.2, any Micronaut application is ready to be built into a native image using the Micronaut Gradle or Maven plugins. To get started, create a new application:

Creating a GraalVM Native Microservice

  1. $ mn create-app hello-world

You can use --build maven for a Maven build.

Building a Native Image Using Docker

To build your native image using Gradle and Docker, run:

Building a Native Image with Docker and Gradle

  1. $ ./gradlew dockerBuildNative

To build your native image using Maven and Docker, run:

Building a Native Image with Docker and Maven

  1. $ ./mvnw package -Dpackaging=docker-native

Building a Native Image Without Using Docker

To build your native image without using Docker, install the GraalVM SDK via the Getting Started instructions or using Sdkman!:

Installing GraalVM 21.2.0 with SDKman

  1. $ sdk install java 21.2.0.r11-grl
  2. $ sdk use java 21.2.0.r11-grl

The native-image tool was extracted from the base GraalVM distribution and is available as a plugin. To install it, run:

Installing native-image tool

  1. $ gu install native-image

Now you can build a native image with Gradle by running the nativeImage task:

Creating native image with Gradle

  1. $ ./gradlew nativeImage

The native image will be built in the build/native-image directory.

To create a native image with Maven and the Micronaut Maven plugin, use the native-image packaging format:

Creating native image with Maven

  1. $ ./mvnw package -Dpackaging=native-image

which builds the native image in the target directory.

You can then run the native image from the directory where you built it.

Run native image

  1. $ ./hello-world

Understanding Micronaut and GraalVM

Micronaut itself does not rely on reflection or dynamic classloading, so it works automatically with GraalVM native, however certain third-party libraries used by Micronaut may require additional input about uses of reflection.

Micronaut includes an annotation processor that helps to generate the reflect-config.json metadata files that are automatically picked up by the native-image tool:

  1. annotationProcessor("io.micronaut:micronaut-graal")
  1. <annotationProcessorPaths>
  2. <path>
  3. <groupId>io.micronaut</groupId>
  4. <artifactId>micronaut-graal</artifactId>
  5. </path>
  6. </annotationProcessorPaths>

This processor generates:

  • A reflect-config.json file in the META-INF/native-image directory in your build classes directory (target/classes with Maven and typically build/classes/java/main with Gradle).

For example the following class:

  1. package example;
  2. import io.micronaut.core.annotation.ReflectiveAccess;
  3. @ReflectiveAccess
  4. class Test {
  5. ...
  6. }

The above example results in the public methods, declared fields and declared constructors of example.Test being included in reflect-config.json.

If you have more advanced requirements and only wish to include certain fields or methods, use the annotation on any constructor, field or method to include only the specific field, constructor or method.

To provide your own reflect.json, add one to src/main/graal/reflect.json and it will be automatically picked up.

Adding Additional Classes for Reflective Access

To inform Micronaut of additional classes to be included in the generated reflect.json file at compile time, either annotate a class with @ReflectiveAccess or @TypeHint.

Both allows for reflective access, and the latter is typically used on a module or Application class to include classes that are needed reflectively. For example, the following is from Micronaut’s Jackson module:

  1. @TypeHint(
  2. value = { (1)
  3. PropertyNamingStrategy.UpperCamelCaseStrategy.class,
  4. ArrayList.class,
  5. LinkedHashMap.class,
  6. HashSet.class
  7. },
  8. accessType = TypeHint.AccessType.ALL_DECLARED_CONSTRUCTORS (2)
  9. )
1The value member specifies which classes require reflection.
2The accessType member specifies if only classloading access is needed or whether full reflection on all public members is needed.

Generating Native Images

GraalVM’s native-image command generates native images. You can use this command manually to generate your native image. For example:

The native-image command

  1. native-image --class-path build/libs/hello-world-0.1-all.jar (1)
1The class-path argument refers to the Micronaut shaded JAR

Once the image is built, run the application using the native image name:

Running the Native Application

  1. $ ./hello-world
  2. 15:15:15.153 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 14ms. Server Running: http://localhost:8080

As you can see, the native image startup completes in milliseconds, and memory consumption does not include the overhead of the JVM (a native Micronaut application runs with just 20mb of memory).

Resource file generation

Starting in Micronaut 3.0 the automatic generation of the resource-config.json file is now part of the Gradle and Maven plugins.