Creating your first Micronaut app

Learn how to create a Hello World Micronaut app with Java with a controller and a functional test.

Authors: Sergio del Amo

Micronaut Version: 2.0.0.RC1

1 Getting Started

This guide uses Micronaut 2.x. You can read this tutorial for Micronaut 1.x.

In this guide we are going to create a Micronaut app written in Java.

1.1 What you will need

To complete this guide, you will need the following:

  • Some time on your hands

  • A decent text editor or IDE

  • JDK 1.8 or greater installed with JAVA_HOME configured appropriately

1.2 Solution

We recommend you to follow the instructions in the next sections and create the app step by step. However, you can go right to the completed example.

or

Then, cd into the complete folder which you will find in the root project of the downloaded/cloned project.

2 Writing the App

Create an app using the Micronaut Command Line Interface.

mn create-app example.micronaut.complete

The previous command creates a micronaut app with the default package example.micronaut in a folder named complete.

By default, create-app creates a Java Micronaut app that uses the Gradle build system. However, you could use other build tools such as Maven or other programming languages such as Groovy or Kotlin.

If you are using Java or Kotlin and IntelliJ IDEA make sure you have enabled annotation processing.

annotationprocessorsintellij

3 Application

Application.java is used when running the application via Gradle or via deployment. You can also run the main class directly within your IDE if it is configured correctly.

src/main/java/example/micronaut/Application.java
package example.micronaut;

import io.micronaut.runtime.Micronaut;

public class Application {

    public static void main(String[] args) {
        Micronaut.run(Application.class);
    }
}

4 Controller

In order to create a microservice that responds with "Hello World" you first need a controller.

Create a Controller:

src/main/java/example/micronaut/HelloController.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") (1)
public class HelloController {
    @Get (2)
    @Produces(MediaType.TEXT_PLAIN) (3)
    public String index() {
        return "Hello World"; (4)
    }
}
1 The class is defined as a controller with the @Controller annotation mapped to the path /hello
2 The @Get annotation is used to map the index method to all requests that use an HTTP GET
3 By default a Micronaut’s response uses application/json as Content-Type. We are returning a String not a JSON object. Because of that, we set it to text/plain.
4 A String "Hello World" is returned as the result

5 Test

Create a Junit test which verifies that when you do a GET request to /hello you get Hello World as a response:

src/test/java/example/micronaut/HelloControllerTest.java
package example.micronaut;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

import io.micronaut.http.HttpRequest;
import io.micronaut.http.client.RxHttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.test.annotation.MicronautTest;
import org.junit.jupiter.api.Test;

import javax.inject.Inject;

@MicronautTest (1)
public class HelloControllerTest {

    @Inject
    @Client("/")
    RxHttpClient client; (2)

    @Test
    public void testHello() {
        HttpRequest<String> request = HttpRequest.GET("/hello"); (3)
        String body = client.toBlocking().retrieve(request);

        assertNotNull(body);
        assertEquals("Hello World", body);
    }
}
1 Annotate the class with @MicronautTest so Micronaut will initialize the application context and the embedded server.
2 Inject the RxHttpClient bean. It is used the execute an HTTP call to the controller.
3 Creating HTTP Requests is easy thanks to Micronaut’s fluid API.

6 Testing the Application

To run the tests:

$ ./gradlew test
$ open build/reports/tests/test/index.html

7 Running the Application

To run the application use the ./gradlew run command which will start the application on port 8080.

8 Generate a Micronaut app's Native Image with GraalVM

We are going to use GraalVM, the polyglot embeddable virtual machine, to generate a Native image of our Micronaut application.

Native images compiled with GraalVM ahead-of-time improve the startup time and reduce the memory footprint of JVM-based applications.

8.1 Micronaut + GraalVM changes

Micronaut itself does not rely on reflection or dynamic classloading so works automatically with GraalVM native.

First, add Micronaut Graal’s annotation processor that helps to handle generating the reflection-config.json metadata that is automatically picked up by the native-image tool:

build.gradle
dependencies {
...
..
.
    annotationProcessor("io.micronaut:micronaut-graal")
}

GraalVM Native Image allows you to ahead-of-time compile Java code to a standalone executable, called a native image. This executable includes the application, the libraries, the JDK and does not run on the Java VM, but includes necessary components like memory management and thread scheduling from a different virtual machine, called “Substrate VM”. Substrate VM is the name for the runtime components (like the deoptimizer, garbage collector, thread scheduling etc.). The resulting program has faster startup time and lower runtime memory overhead compared to a Java VM.

We need to add a dependency to svm:

build.gradle
dependencies {
...
..
.
    compileOnly("org.graalvm.nativeimage:svm")
}

To simplify building the image you need to create a native-image.properties file. The convention is to use the folder src/main/resources/META-INF/native-image and then a folder following the maven coordinates of the application. For our example example.micronaut/micronautguide.

Add a native-image.properties inside src/main/resources/META-INF/native-image/example.micronaut/micronautguide folder.

src/main/resources/META-INF/native-image/example.micronaut/micronautguide/native-image.properties
Args = -H:Name=micronautguide \
       -H:Class=example.micronaut.Application
  • The -H:Name argument specifies the name of the native image that will be generated.

  • The -H:Class argument specifies the entry point of your application (the class that defines a static void main method.

8.2 Native Image generation

The easiest way to install GraalVM is to use SDKMan.io.

$ sdk list java
================================================================================
Available Java Versions
================================================================================
 Vendor        | Use | Version      | Dist    | Status     | Identifier
--------------------------------------------------------------------------------
....
 GraalVM       |     | 20.1.0.r11   | grl     | installed  | 20.1.0.r11-grl
               |     | 20.1.0.r8    | grl     |            | 20.1.0.r8-grl
               |     | 20.0.0.r11   | grl     |            | 20.0.0.r11-grl
               |     | 20.0.0.r8    | grl     | installed  | 20.0.0.r8-grl
               |     | 19.3.1.r11   | grl     | installed  | 19.3.1.r11-grl
               |     | 19.3.1.r8    | grl     |            | 19.3.1.r8-grl
....

# For Java 8
$ sdk install java 20.1.0.r8-grl

# For Java 11
$ sdk install java 20.1.0.r11-grl

You need to install the native-image component which is not installed by default.

$ gu install native-image

To generate the native image you need to generate the FAT JAR first.

$ ./gradlew assemble

Invoke native-image. It may take a minute to complete.

$ which java
/Users/sdelamo/.sdkman/candidates/java/20.1.0.r11-grl/bin/java
$ native-image --no-server -cp build/libs/complete-0.1-all.jar
[micronautguide:28833]    classlist:   3,459.84 ms,  0.96 GB
[micronautguide:28833]        (cap):   2,622.25 ms,  0.96 GB
[micronautguide:28833]     (clinit):   1,306.37 ms,  5.48 GB
[micronautguide:28833]   (typeflow):  14,735.93 ms,  5.48 GB
[micronautguide:28833]    (objects):  21,228.62 ms,  5.48 GB
[micronautguide:28833]   (features):   2,722.65 ms,  5.48 GB
[micronautguide:28833]     analysis:  42,115.37 ms,  5.48 GB
[micronautguide:28833]     universe:   1,348.45 ms,  5.48 GB
[micronautguide:28833]      (parse):   2,717.52 ms,  5.48 GB
[micronautguide:28833]     (inline):   4,520.18 ms,  5.64 GB
[micronautguide:28833]    (compile):  20,724.72 ms,  5.90 GB
[micronautguide:28833]      compile:  30,636.91 ms,  5.90 GB
[micronautguide:28833]        image:   5,010.54 ms,  5.66 GB
[micronautguide:28833]        write:   1,399.49 ms,  5.66 GB
[micronautguide:28833]      [total]:  90,218.13 ms,  5.66 GB

--no-server options tells to not use server-based image building.

You can invoke the generated native image micronautguide. Startup will be really fast.

 $ ./micronautguide -Xmx68m
07:42:20.792 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 21ms. Server Running: http://localhost:8080

You can execute the endpoint exposed by the native image:

$ curl localhost:8080/hello
Hello World

8.3 Native Image generation with Docker

Alternatively, you can use Dockerfile to construct the native image and a script docker-build.sh to run it:

Dockerfile
FROM oracle/graalvm-ce:20.1.0-java8 as graalvm
RUN gu install native-image

COPY . /home/app/complete
WORKDIR /home/app/complete

RUN native-image --no-server -cp build/libs/complete-*-all.jar

FROM frolvlad/alpine-glibc
RUN apk update && apk add libstdc++
EXPOSE 8080
COPY --from=graalvm /home/app/complete/complete /app/complete
ENTRYPOINT ["/app/complete"]
docker-build.sh
#!/bin/sh
docker build . -t complete
echo
echo
echo "To run the docker container execute:"
echo "    $ docker run -p 8080:8080 complete"
$ ./gradlew assemble

$ % ./docker-build.sh
Sending build context to Docker daemon    127MB
Step 1/10 : FROM oracle/graalvm-ce:20.1.0-java11 as graalvm
 ---> 2adef7aab8f9
Step 2/10 : RUN gu install native-image
 ---> Using cache
 ---> a52c98379916
Step 3/10 : COPY . /home/app/complete
 ---> 7b5d8708feb7
Step 4/10 : WORKDIR /home/app/complete
 ---> Running in 52615dd2ceed
Removing intermediate container 52615dd2ceed
 ---> fe6e67e150b6
Step 5/10 : RUN native-image --no-server -cp build/libs/complete-*-all.jar
 ---> Running in b0cca1a82c2c
[micronautguide:24]    classlist:   4,224.09 ms,  0.96 GB
[micronautguide:24]        (cap):     557.93 ms,  0.96 GB
WARNING: Could not resolve io.netty.channel.epoll.EpollChannelOption for reflection configuration.
WARNING: Could not resolve io.netty.channel.kqueue.KQueueChannelOption for reflection configuration.
[micronautguide:24]        setup:   1,840.06 ms,  0.96 GB
WARNING GR-10238: VarHandle for static field is currently not fully supported. Static field private static volatile java.lang.System$Logger jdk.internal.event.EventHelper.securityLogger is not properly marked for Unsafe access!
[micronautguide:24]     (clinit):   1,383.94 ms,  3.42 GB
[micronautguide:24]   (typeflow):  16,198.26 ms,  3.42 GB
[micronautguide:24]    (objects):  20,293.44 ms,  3.42 GB
[micronautguide:24]   (features):   2,859.27 ms,  3.42 GB
[micronautguide:24]     analysis:  43,474.70 ms,  3.42 GB
[micronautguide:24]     universe:   1,783.90 ms,  3.38 GB
[micronautguide:24]      (parse):   4,345.14 ms,  3.68 GB
[micronautguide:24]     (inline):   2,786.82 ms,  3.52 GB
[micronautguide:24]    (compile):  18,459.86 ms,  4.32 GB
[micronautguide:24]      compile:  28,292.51 ms,  4.32 GB
[micronautguide:24]        image:   5,348.95 ms,  4.33 GB
[micronautguide:24]        write:     804.47 ms,  4.33 GB
[micronautguide:24]      [total]:  86,008.19 ms,  4.33 GB
Removing intermediate container b0cca1a82c2c
 ---> 31e41f3f3306
Step 6/10 : FROM frolvlad/alpine-glibc
 ---> 3a1c752f649f
Step 7/10 : RUN apk update && apk add libstdc++
 ---> Using cache
 ---> 536cd2f06071
Step 8/10 : EXPOSE 8080
 ---> Using cache
 ---> 1e011af81857
Step 9/10 : COPY --from=graalvm /home/app/complete/micronautguide /app/complete
 ---> 921480edd9b7
Step 10/10 : ENTRYPOINT ["/app/complete"]
 ---> Running in a61b95562776
Removing intermediate container a61b95562776
 ---> 56c8fff02b03
Successfully built 56c8fff02b03
Successfully tagged complete:latest


To run the docker container execute:
    $ docker run -p 8080:8080 complete

You can use docker to run the image with the app’s native-image:

sdelamo@Sergios-iMac-Pro complete %  docker run -p 8080:8080 complete
/app/complete: /usr/lib/libstdc++.so.6: no version information available (required by /app/complete)
05:06:22.567 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 40ms. Server Running: http://35bc084914f6:8080

9 Next steps

Read more about Micronaut testing.

10 Help with Micronaut

OCI sponsored the creation of this Guide. OCI offers several Micronaut services:

Free consultation

The OCI Micronaut Team includes Micronaut co-founders, Jeff Scott Brown and Graeme Rocher. Check our Micronaut courses and learn from the engineers who developed, matured and maintain Micronaut.

Micronaut OCI Team