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: 1.3.1

1 Getting Started

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:IncludeResources=logback.xml|application.yml \
       -H:Name=micronautguide \
       -H:Class=example.micronaut.Application
  • The -H:IncludeResources argument allows you to tweak which static resources to include. You can use regular expressions.

  • 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.0.0.r11   | grl     | installed  | 20.0.0.r11-grl
               |     | 20.0.0.r8    | grl     | installed  | 20.0.0.r8-grl
               |     | 19.3.1.r11   | grl     |            | 19.3.1.r11-grl
               |     | 19.3.1.r8    | grl     |            | 19.3.1.r8-grl
....

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

# For Java 11
$ sdk install java 20.0.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.0.0.r8-grl/bin/java
$ native-image --no-server -cp build/libs/complete-0.1-all.jar
[micronautguide:39362]    classlist:   3,884.97 ms,  1.69 GB
[micronautguide:39362]        (cap):   5,919.80 ms,  1.69 GB
[micronautguide:39362]        setup:   7,222.88 ms,  1.69 GB
[micronautguide:39362]   (typeflow):  14,037.78 ms,  4.05 GB
[micronautguide:39362]    (objects):  17,689.40 ms,  4.05 GB
[micronautguide:39362]   (features):   1,751.37 ms,  4.05 GB
[micronautguide:39362]     analysis:  35,421.99 ms,  4.05 GB
[micronautguide:39362]     (clinit):     978.01 ms,  4.09 GB
[micronautguide:39362]     universe:   2,114.65 ms,  4.09 GB
[micronautguide:39362]      (parse):   2,327.75 ms,  4.09 GB
[micronautguide:39362]     (inline):   2,153.46 ms,  5.81 GB
[micronautguide:39362]    (compile):  13,066.94 ms,  7.08 GB
[micronautguide:39362]      compile:  19,714.97 ms,  7.15 GB
[micronautguide:39362]        image:   3,509.84 ms,  7.15 GB
[micronautguide:39362]        write:   1,114.96 ms,  7.15 GB
[micronautguide:39362]      [total]:  73,294.82 ms,  7.15 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.0.0-java8 as graalvm
#FROM oracle/graalvm-ce:20.0.0-java11 as graalvm # For JDK 11
RUN gu install native-image

COPY . /home/app/micronautguide
WORKDIR /home/app/micronautguide

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/micronautguide/micronautguide /micronautguide/micronautguide
ENTRYPOINT ["/micronautguide/micronautguide", "-Xmx68m"]
docker-build.sh
#!/bin/sh
docker build . -t micronautguide
echo
echo
echo "To run the docker container execute:"
echo "    $ docker run -p 8080:8080 micronautguide"
$ ./gradlew assemble

$ ./docker-build.sh
Sending build context to Docker daemon  64.67MB
Step 1/10 : FROM oracle/graalvm-ce:20.0.0-java8 as graalvm
 ---> 1515e8e21105
Step 2/10 : RUN gu install native-image
 ---> Running in 805c70f45a9d
Downloading: Component catalog from www.graalvm.org
Processing Component: Native Image
Downloading: Component native-image: Native Image  from github.com
Installing new component: Native Image (org.graalvm.native-image, version 20.0.0)
Refreshed alternative links in /usr/bin/
Removing intermediate container 805c70f45a9d
 ---> b098407fe3b2
Step 3/10 : COPY . /home/app/micronautguide
 ---> a4ac50285c3e
Step 4/10 : WORKDIR /home/app/micronautguide
 ---> Running in 5bcaea479a47
Removing intermediate container 5bcaea479a47
 ---> 7683d318359a
Step 5/10 : RUN native-image --no-server -cp build/libs/micronautguide-*-all.jar
 ---> Running in f287939a526a
[micronautguide:28]    classlist:   8,773.36 ms
[micronautguide:28]        (cap):   1,044.04 ms
[micronautguide:28]        setup:   2,748.03 ms
[micronautguide:28]   (typeflow):  20,611.56 ms
[micronautguide:28]    (objects):  20,377.15 ms
[micronautguide:28]   (features):   2,168.30 ms
[micronautguide:28]     analysis:  45,559.16 ms
[micronautguide:28]     (clinit):   1,141.31 ms
[micronautguide:28]     universe:   2,719.10 ms
[micronautguide:28]      (parse):   2,808.15 ms
[micronautguide:28]     (inline):   3,094.66 ms
[micronautguide:28]    (compile):  25,648.31 ms
[micronautguide:28]      compile:  34,018.50 ms
[micronautguide:28]        image:   3,811.46 ms
[micronautguide:28]        write:     643.59 ms
[micronautguide:28]      [total]:  99,095.08 ms
Removing intermediate container f287939a526a
 ---> 20a8c370d984
Step 6/10 : FROM frolvlad/alpine-glibc
 ---> 38dd85a430e8
Step 7/10 : RUN apk update && apk add libstdc++
 ---> Running in 2a3b22d15e04
fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/community/x86_64/APKINDEX.tar.gz
v3.10.4-15-g5600c80ab9 [http://dl-cdn.alpinelinux.org/alpine/v3.10/main]
v3.10.4-7-g6ee081a8fb [http://dl-cdn.alpinelinux.org/alpine/v3.10/community]
OK: 10349 distinct packages available
(1/1) Installing libstdc++ (8.3.0-r0)
Executing glibc-bin-2.30-r0.trigger
/usr/glibc-compat/sbin/ldconfig: /usr/glibc-compat/lib/ld-linux-x86-64.so.2 is not a symbolic link

OK: 18 MiB in 18 packages
Removing intermediate container 2a3b22d15e04
 ---> 913d6350ff6d
Step 8/10 : EXPOSE 8080
 ---> Running in 7f3cdddebf26
Removing intermediate container 7f3cdddebf26
 ---> 5b82cacacbfb
Step 9/10 : COPY --from=graalvm /home/app/micronautguide/micronautguide /app/micronautguide
 ---> 5506ff660b65
Step 10/10 : ENTRYPOINT ["/app/micronautguide"]
 ---> Running in ef23d1eee195
Removing intermediate container ef23d1eee195
 ---> fb30459595cb
Successfully built fb30459595cb
Successfully tagged micronautguide:latest


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

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

 docker run -p 8080:8080 micronautguide
06:58:26.977 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 27ms. Server Running: http://f8143044b1ee: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