Introduction to Micronaut – A new JVM framework


Recently, I discovered a new framework in Java land called Micronaut. Being a Java developer for last fourteen years, I have seen many Java frameworks come and go. Apart from Spring, there are very few frameworks that made a mark. After a long time, I discovered a framework that is challenging the status quo of web development in Java.

Micronaut is an open-source modern JVM framework for building modular, easily testable microservice and serverless applications. It is a truly polyglot framework. You can build applications in Java, Kotlin, and Groovy.

micronaut-logo-white

Why you should learn Micronaut?

There have to be valid reasons why should learn a new framework. The reason why I decided to learn Micronaut are:

Reason 1: Fast startup time & reduced memory footprint

You might be wondering if it really matters to have fast startup time. In the old times, most JVMs ran on servers for a long time. So for most applications, it didn’t matter if application took few more seconds to start. Nowadays, we build applications that are composed of many small services. These services run in dynamic cloud environments orchestrated by container orchestration solution like ECS or Kubernetes. This requires your applications to be able to come up and down in no time.

To make it concrete, let’s compare start up times of Micronaut and Spring Boot. We will assume that we have two hello-world applications – one created with Micronaut and other with Spring Boot. You can generate a new Spring Boot application with just web dependency from https://start.spring.io/. For Micronaut, you can use its command-line. In a later section, I will show you how to create the application in a step by step manner. For now just assume that you can create similar hello-world applications with Spring Boot and Micronaut. Both of these framework allows you to create executable jars. Let’s assume that for Spring Boot our jar is hello-world-boot.jar and for Micronaut it is hello-world-micronaut.jar.

To run the hello-world Spring Boot jar, you need at least 16M of heap size.

java -Xmx16M -jar hello-world-boot.jar
 .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.1.RELEASE)

2018-12-08 13:25:41.570  INFO 90020 --- [           main] com.example.demo.DemoApplication         : Starting DemoApplication on shekhargulati.local with PID 90020
2018-12-08 13:25:41.574  INFO 90020 --- [           main] com.example.demo.DemoApplication         : No active profile set, falling back to default profiles: default
2018-12-08 13:25:42.835  INFO 90020 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2018-12-08 13:25:42.982  INFO 90020 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2018-12-08 13:25:42.983  INFO 90020 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/9.0.13
2018-12-08 13:25:43.037  INFO 90020 --- [           main] o.a.catalina.core.AprLifecycleListener   : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path:
2018-12-08 13:25:43.989  INFO 90020 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2018-12-08 13:25:43.989  INFO 90020 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 2373 ms
2018-12-08 13:25:46.059  INFO 90020 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2018-12-08 13:25:52.085  INFO 90020 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2018-12-08 13:25:52.151  INFO 90020 --- [           main] com.example.demo.HelloWorldBootApplication         : Started HelloWorldBootApplication in 10.944 seconds

As you can see it took close to 11 seconds to start the Spring Boot application with 16M of heap memory. If you give Spring Boot application 1G of memory then application can start under 2 seconds. This is the best Spring Boot application can do.

On the other hand, Micronaut application can start in 1 second with only 7M of heap memory.

$ java -Xmx7M -jar hello-world-micronaut.jar
13:27:47.487 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 1015ms. Server Running: http://localhost:8080

To understand why Micronaut has faster startup compared to Spring Boot we have to understand what JVM does at startup. As mentioned in this post by PurelyFunctional people, JVM does three things at startup:

  1. Allocate memory for the initial heap
  2. Load classes that application needs to run
  3. Initialise classes loaded by it step 2

Assuming we give same initial heap size to both Micronaut and Spring Boot applications, the points to focus are 2 and 3.

Load classes that application needs to run

To find number of classes loaded by Spring Boot, we can run the following command.

$ java -verbose:class -jar hello-world-boot.jar | grep Loaded

Spring Boot loads close to 6640 classes.

To find number of classes loaded by Micronaut, we can run the following command.

$ java -verbose:class -jar hello-world-micronaut.jar | grep Loaded

Micronaut loads close to 5900 classes.

Overall, there is not much difference between number of classes loaded by Spring Boot and Micronaut.

Initialise loaded classes

This is the process that takes time in Spring Boot. At server boot up, Spring dependency injection container creates beans. Spring dependency injection is based on runtime reflection and proxies. Spring caches the metadata so that it does not have to use reflection again but you pay the cost at startup.

Micronaut like Spring and Grails also supports dependency injection. Dependency injection changed the way Java developers build applications. But unlike Spring and Grails, Micronaut does dependency injection at compile time. Micronaut uses annotation processors to precompile the necessary metadata required for DI.

Micronaut borrows the idea of compile time dependency injection from Dagger. Dagger is a fully static, compile-time dependency injection framework for both Java and Android. It is an adaptation of an earlier version created by Square and now maintained by Google.

Micronaut supports the following types of dependency injection:

  • Constructor injection (must be one public constructor or a single contructor annotated with @Inject)
  • Field injection
  • JavaBean property injection
  • Method parameter injection

You can read more about Micronaut DI support in its documentation.

Reason 2: Future ready

Micronaut is written from the ground up for building modern Java applications. The low memory footprint and faster startup times discussed above makes it suitable to running in scenarios such as Serverless functions or low memory footprint reactive Microservices.

Reason 3: Micronaut == Spring Boot + Spring Cloud

Micronaut API is similar to Spring Boot. Most of the Java developers are friendly with Spring Boot. So, it is easy for them to start working with Micronaut as well. Micronaut also supports cloud native features like service discovery, client side load balancing, distributed tracing. This gives you all the toolset required to build modern Java web applications.

Installing Micronaut

Micronaut comes with a command-line client that you can use to scaffold applications. The Micronaut CLI can be installed using SDKMan. To install SDKMAN, you can refer to install instructions mentioned on their website. If you are Mac or Unix platform then you can install SDKMAN using following command:

$ curl -s "https://get.sdkman.io" | bash

Next, open a new terminal or enter:

$ source "$HOME/.sdkman/bin/sdkman-init.sh"

Once SDKMan is installed, you can install Micronaut CLI using the following command

$ sdk install micronaut

Once installed, you can use the Micronaut CLI using the mn command as shown below.

$ mn
| Starting interactive mode...
| Enter a command name to run. Use TAB for completion:
mn>

Step 1: Creating a new Micronaut app

To create a new Micronaut app, you can run the following command

mn> create-app todo-service --lang=java --build=gradle --features=junit

As you can see from the above command, we created a new Java application that uses Gradle as the build tool. We told Micronaut that we want to use JUnit as our testing framework.

The above command is equivalent to the following command as Java, Gradle, and JUnit are default values.

mn> create-app todo-service

Micronaut supports three languages – Java, Kotlin, and Groovy so you pick them as well. Java is the default choice.

Similarly, Micronaut supports both Maven and Gradle but Gradle is the default choice.

Micronaut supports JUnit and Spock as the two testing frameworks. JUnit is the default choice.

Once application is created, you can open the app in your IDE. If you use IntelliJ then you can open the app using command-line. Please exit from the Micronaut shell and change directory to todo-service.

$ cd todo-service
$ idea .

This will open the application in IntelliJ Idea.

Step 2: Running the application

To run the application, you can either do that via IDE or Gradle. To run the application using Gradle, execute the following command in root folder of your application.

$ ./gradlew run

This should start the application in under 1 second. This the main selling point of Micronaut. Micronaut does compile time dependency injection so it avoid creating proxies at run time.

On MacOS, you might notice that the application takes more than 10 second to start. This is due because on macOS Sierra and above, java.net.InetAddress:getLocalHost is slow. The solution for this is to modify your /etc/hosts file and add following two lines.

127.0.0.1   localhost mac.local
::1         localhost mac.local

Replace mac.local with your hostname. You can find host name of you machine by running hostname command.

You can read more about this issue in the post by Antonio Troina. Thanks Antonio.

After you make the change, run you app again and this time you will see application started in less than a second.

$  ./gradlew run

> Task :run
18:30:59.539 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 726ms. Server Running: http://localhost:8080

As you can see above, application started in 726ms.

Another point in favour of Micronaut is the size of the executable JAR. If you build the application then you can create the executable JAR.

$ ./gradlew clean build

The executable JAR is created inside build/libs directory.

$ ll build/libs
-rw-r--r--  1 shekhargulati  staff    11M Dec  5 18:31 todo-service-0.1-all.jar

As you can see, application JAR is 11M only.

Step 3: Creating a REST controller

We can use Micronaut CLI to create a new controller for us. As you will notice, Micronaut uses Spring terminology. You can also create the class using your IDE.

mn> create-controller todo.service.controllers.TodoController

This will create TodoController and TodoControllerTest classes. The code for TodoController is shown below.

package todo.service.controllers;

import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.HttpStatus;

@Controller("/todo")
public class TodoController {

    @Get("/")
    public HttpStatus index() {
        return HttpStatus.OK;
    }
}

The above exposes a GET REST endpoint at /todo. When you will make a GET call to /todo then HTTP status code 200 will be returned.

The generated test case for the above code is shown below

package todo.service.controllers;

import io.micronaut.context.ApplicationContext;
import io.micronaut.http.HttpStatus;
import io.micronaut.http.client.RxHttpClient;
import io.micronaut.runtime.server.EmbeddedServer;
import org.junit.Test;


import static org.junit.Assert.assertEquals;

public class TodoControllerTest {

    @Test
    public void testIndex() throws Exception {
        try(EmbeddedServer server = ApplicationContext.run(EmbeddedServer.class)) {
            try(RxHttpClient client = server.getApplicationContext().createBean(RxHttpClient.class, server.getURL())) {
                assertEquals(HttpStatus.OK, client.toBlocking().exchange("/todo").status());
            }
        }
    }
}

The code shown above creates a Reactive HTTP Client. Micronaut uses RxJava to provide reactive HTTP client.

If you will run this code using your IDE then you might see that test case will fail because of Page not found error. The error happens because we have not enabled annotation processor in IntelliJ.

Once enabled, you should be able to run the test and it will be green.

Conclusion

In this post, we learnt about a new JVM framework — Micronaut. We started with listing reasons why Java developers should learn Micronaut. Then, we created a hello world Micronaut application using its CLI tool. In the next post, we will build a fully feature Micronaut application.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: