Creating a Graal Native Microservice

    The graalvm feature adds three important items:

    1. The svm and graal dependencies to your build.gradle (or pom.xml if --build maven is used).

    2. A Dockerfile which can be used to construct the native image using Docker and a script docker-build.sh to run it.

    3. A native-image.properties configuration file src/main/resources/META-INF/native-image to simplify building the image.

    The native-image.properties file that is generated looks something like:

    1. Args = -H:Name=example \ (1)
    2. -H:Class=example.Application (2)

    Building a Native Image Using Docker

    To build your native image using Docker simply run:

    1. $ ./gradlew assemble # or ./mvnw package if using Maven
    2. $ docker build . -t hello-world
    3. $ docker run -p 8080:8080 hello-world

    Or use the provided script:

    1. $ ./docker-build.sh

    The provided Dockerfile is a multi-stage dockerfile which builds the project in two steps:

    1. A GraalVM official image will build the native image.

    Building a Native Image Without Using Docker

    To build your native image without using Docker you need to install GraalVM SDK via the instructions or using Sdkman!:

    Installing GraalVM 20.1.0 with SDKman

    1. $ sdk install java 20.1.0.r8-grl # or 20.1.0.r11-grl if you want to use JDK 11
    2. $ sdk use java 20.1.0.r8-grl

    The tool was extracted from the base GraalVM distribution. Currently it is available as an early adopter plugin. To install it, run:

    Installing native-image tool

    Creating native image with Gradle

    1. $ ./gradlew assemble
    2. $ native-image --no-server -cp build/libs/hello-world-0.1-all.jar

    Creating native image with Maven

    1. $ ./mvnw package
    2. $ native-image --no-server -cp target/hello-world-0.1-all.jar

    Run native image

    1. $ ./hello-world

    Micronaut itself does not rely on reflection or dynamic classloading so 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 handle generating the reflection-config.json and resource-config.json metadata files that are automatically picked up by the native-image tool:

    1. annotationProcessor("io.micronaut:micronaut-graal")

    This processor will generate: - A reflection-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). - A native-image.properties file to read this configuration for all classes annotated with either or @TypeHint. - A resource-config.json file also in the META-INF/native-image directory in your build classes directory containing all the files in the src/main/resources file.

    1. package example;
    2. import io.micronaut.core.annotation.*;
    3. class Test {
    4. ...

    The above example will result in the public methods and declared constructors of example.Test being included in reflection-config.json.

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

    Stating with Micronaut 2.0, as the framework generates automatically the file resource-config.json, if you want to include your own additional resources you can provide them in src/main/graal/resource-config.json and they will automatically added to the generated file.

    To inform Micronaut of additional classes that should be included in the generated reflect.json file at compilation time you can either annotate a class with @Introspected or .

    The former will generate a compile time introspection as well as allowing reflective access and the latter will only allow reflective access and is typically used on a module or Application class to include classes that are needed reflectively. For example, the following is taken 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. )

    GraalVM’s native-image command is used to generate native images. You can use this command manually to generate your native image. An example can be seen below.

    The native-image command

    1. native-image --no-server \ (1)
    2. --class-path build/libs/hello-world-0.1-all.jar (2)

    Once the image has been built you can run the application using the native image name:

    Running the Native Application

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

    As you can see the advantage of having a native image is 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).