1. What Is Micronaut

Micronaut is a JVM-based framework for building lightweight, modular applications. Developed by OCI, the same company that created Grails, Micronaut is the latest framework designed to make creating microservices quick and easy.

While Micronaut contains some features that are similar to existing frameworks like Spring, it also has some new features that set it apart. And with support for Java, Groovy, and Kotlin, it offers a variety of ways to create applications.

2. Main Features

One of the most exciting features of Micronaut is its compile time dependency injection mechanism. Most frameworks use reflection and proxies to perform dependency injection at runtime. Micronaut, however, builds its dependency injection data at compile time. The result is faster application startup and smaller memory footprints.

Another feature is its first class support for reactive programming, for both clients and servers. The choice of a specific reactive implementation is left to the developer as both RxJava and Project Reactor are supported.

Micronaut also has several features that make it an excellent framework for developing cloud-native applications. It supports multiple service discovery tools such as Eureka and Consul, and also works with different distributed tracing systems such as Zipkin and Jaeger.

It also provides support for creating AWS lambda functions, making it easy to create serverless applications.

3. Getting Started

The easiest way to get started is using SDKMAN:

> sdk install micronaut 1.0.0.RC2

This installs all the binary files we’ll need to build, test, and deploy Micronaut applications. It also provides the Micronaut CLI tool, which lets us easily start new projects.

The binary artifacts are also available on Sonatype and GitHub.

In the following sections we’ll look at some features of the framework.

4. Dependency Injection

As mentioned earlier, Micronaut handles dependency injection at compile time, which is different than most IoC containers.

However, it still fully supports JSR-330 annotations so working with beans is similar to other IoC frameworks.

To autowire a bean into our code, we use @Inject:

@Inject
private EmployeeService service;

The @Inject annotation works just like @Autowired and can be used on fields, methods, constructors, and parameters.

By default, all beans are scoped as a prototype. We can quickly create singleton beans using @Singleton. If multiple classes implement the same bean interface, @Primary can be used to deconflict them:

@Primary
@Singleton
public class BlueCar implements Car {}

The @Requires annotation can be used when beans are optional, or to only perform autowiring when certain conditions are met.

In this regard, it behaves much like the Spring Boot @Conditional annotations:

@Singleton
@Requires(beans = DataSource.class)
@Requires(property = "enabled")
@Requires(missingBeans = EmployeeService)
@Requires(sdk = Sdk.JAVA, value = "1.8")
public class JdbcEmployeeService implements EmployeeService {}

5. Building an HTTP Server

Now let’s look at creating a simple HTTP server application. To start, we’ll use SDKMAN to create a project:

> mn create-app hello-world-server -build maven

This will create a new Java project using Maven in a directory named hello-world-server. Inside this directory, we’ll find our main application source code, Maven POM file, and other support files for the project.

The default application that is very simple:

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

5.1. Blocking HTTP

On its own, this application won’t do much. Let’s add a controller that has two endpoints. Both will return a greeting, but one will use the GET HTTP verb, and the other will use POST:

@Controller("/greet")
public class GreetController {

    @Inject
    private GreetingService greetingService;

    @Get("/{name}")
    public String greet(String name) {
        return greetingService.getGreeting() + name;
    }

    @Post(value = "/{name}", consumes = MediaType.TEXT_PLAIN)
    public String setGreeting(@Body String name) {
        return greetingService.getGreeting() + name;
    }
}

5.2. Reactive IO

By default, Micronaut will implement these endpoints using traditional blocking I/O. However, we can quickly implement non-blocking endpoints by merely changing the return type to any reactive non-blocking type.

For example, with RxJava we can use Observable. Likewise, when using Reactor, we can return Mono or Flux data types:

@Get("/{name}")
public Mono<String> greet(String name) {
    return Mono.just(greetingService.getGreeting() + name);
}

For both blocking and non-blocking endpoints, Netty is the underlying server used to handle HTTP requests.

Normally, the requests are handled on the main I/O thread pool that is created at startup, making them block.

However, when a non-blocking data type is returned from a controller endpoint, Micronaut uses the Netty event loop thread, making the whole request non-blocking.

6. Building an HTTP Client

Now let’s build a client to consume the endpoints we just created. Micronaut provides two ways of creating HTTP clients:

  • A declarative HTTP Client
  • A programmatic HTTP Client

6.1. Declarative HTTP Client

The first and quickest way to create is using a declarative approach:

@Client("/greet")
public interface GreetingClient {
    @Get("/{name}")
    String greet(String name);
}

Notice how we don’t implement any code to call our service. Instead, Micronaut understands how to call the service from the method signature and annotations we have provided.

To test this client, we can create a JUnit test that uses the embedded server API to run an embedded instance of our server:

public class GreetingClientTest {
    private EmbeddedServer server;
    private GreetingClient client;

    @Before
    public void setup() {
        server = ApplicationContext.run(EmbeddedServer.class);
        client = server.getApplicationContext().getBean(GreetingClient.class);
    }

    @After
    public void cleanup() {
        server.stop();
    }

    @Test
    public void testGreeting() {
        assertEquals(client.greet("Mike"), "Hello Mike");
    }
}

6.2. Programmatic HTTP Client

We also have the option of writing a more traditional client if we need more control over its behavior and implementation:

@Singleton
public class ConcreteGreetingClient {
   private RxHttpClient httpClient;

   public ConcreteGreetingClient(@Client("/") RxHttpClient httpClient) {
      this.httpClient = httpClient;
   }

   public String greet(String name) {
      HttpRequest<String> req = HttpRequest.GET("/greet/" + name);
      return httpClient.retrieve(req).blockingFirst();
   }

   public Single<String> greetAsync(String name) {
      HttpRequest<String> req = HttpRequest.GET("/async/greet/" + name);
      return httpClient.retrieve(req).first("An error as occurred");
   }
}

The default HTTP client uses RxJava, so can easily work with blocking or non-blocking calls.

7. Micronaut CLI

We’ve already seen the Micronaut CLI tool in action above when we used it to create our sample project.

In our case, we created a standalone application, but it has several other capabilities as well.

7.1. Federation Projects

In Micronaut, a federation is just a group of standalone applications that live under the same directory. By using federations, we can easily manage them together and ensure they get the same defaults and settings.

When we use the CLI tool to generate a federation, it takes all the same arguments as the create-app command. It will create a top-level project structure, and each standalone app will be created in its sub-directory from there.

7.2. Features

When creating a standalone application or federation, we can decide which features our app needs. This helps ensure the minimal set of dependencies is included in the project.

We specify features using the -features argument and supplying a comma-separated list of feature names.

We can find a list of available features by running the following command:

> mn profile-info service

Provided Features:
--------------------
* annotation-api - Adds Java annotation API
* config-consul - Adds support for Distributed Configuration with Consul
* discovery-consul - Adds support for Service Discovery with Consul
* discovery-eureka - Adds support for Service Discovery with Eureka
* groovy - Creates a Groovy application
[...] More features available

7.3. Existing Projects

We can also use the CLI tool to modify existing projects. Enabling us to create beans, clients, controllers, and more. When we run the mn command from inside an existing project, we’ll have a new set of commands available:

> mn help
| Command Name         Command Description
-----------------------------------------------
create-bean            Creates a singleton bean
create-client          Creates a client interface
create-controller      Creates a controller and associated test
create-job             Creates a job with scheduled method

8. Conclusion

In this brief introduction to Micronaut, we’ve seen how easy it is to build both blocking and non-blocking HTTP servers and clients. Also, we explored some features of its CLI.

But this is just a small taste of the features it offers. There is also full support for serverless functions, service discovery, distributed tracing, monitoring and metrics, a distributed configuration, and much more.

And while many of its features are derived from existing frameworks such as Grails and Spring, it also has plenty of unique features that help it stand out on its own.

As always, we can find the samples code above in our GitHub repo.