Micronaut 2.0: a full stack Java framework for modular, testable applications

栏目: IT技术 · 发布时间: 3年前

内容简介:Micronaut is a modern, JVM-based, full stack Java framework designed for building modular, easily testable JVM applications with support for Java, Kotlin and the Groovy language.Micronaut is developed by the creators of the Grails framework and takes inspi

Micronaut

Natively Cloud Native

Version:

1 Introduction

Micronaut is a modern, JVM-based, full stack Java framework designed for building modular, easily testable JVM applications with support for Java, Kotlin and the Groovy language.

Micronaut is developed by the creators of the Grails framework and takes inspiration from lessons learnt over the years building real-world applications from monoliths to microservices using Spring, Spring Boot and Grails.

Micronaut aims to provide all the tools necessary to build JVM applications including:

  • Dependency Injection and Inversion of Control (IoC)

  • Aspect Oriented Programming (AOP)

  • Sensible Defaults and Auto-Configuration

With Micronaut you can build Message-Driven Applications, Command Line Applications, HTTP Servers and more whilst for Microservices in particular Micronaut also provides:

  • Distributed Configuration

  • Service Discovery

  • HTTP Routing

  • Client-Side Load Balancing

At the same time Micronaut aims to avoid the downsides of frameworks like Spring, Spring Boot and Grails by providing:

  • Fast startup time

  • Reduced memory footprint

  • Minimal use of reflection

  • Minimal use of proxies

  • No runtime bytecode generation

  • Easy Unit Testing

Historically, frameworks such as Spring and Grails were not designed to run in scenarios such as server-less functions, Android apps, or low memory-footprint microservices. In contrast, Micronaut is designed to be suitable for all of these scenarios.

This goal is achieved through the use of Java’s annotation processors , which are usable on any JVM language that supports them, as well as an HTTP Server and Client built on Netty . In order to provide a similar programming model to Spring and Grails, these annotation processors precompile the necessary metadata in order to perform DI, define AOP proxies and configure your application to run in a low-memory environment.

Many of the APIs within Micronaut are heavily inspired by Spring and Grails. This is by design, and aids in bringing developers up to speed quickly.

1.1 What's New?

Micronaut 2.0.0 includes the following changes:

Core Features

Support for JDK 14

Micronaut has been updated to support JDK 14.

Groovy 3

Micronaut now supports applications written in Groovy 3.

Startup Performance Improvements

Startup time has been further improved in this release with typical startup time for a new application around 20% faster.

Improvements to Bean Introspections

Bean introspections have been improved to support static creator methods, interfaces and enums. This means you can define a bean introspection on an interface with a private implementation such as:

Introspections on interfaces

import io.micronaut.core.annotation.Creator;

@io.micronaut.core.annotation.Introspected
interface Example {
    String getName();

    @Creator
    static Example create(String name) {
        return () -> name;
    }
}

Support for Analyzing the Injection Point

Micronaut’s Dependency Injection implementation has been improved such that you can now receive anInjectionPoint instance to any@Factory method. This makes it possible to customize how the bean is created based on the annotation metadata at the point at which the bean is injected.

For example consider the following definition:

@Inject @Client("http://foo.com") RxHttpClient client;

A factory method can receive the injection point and create a client based off of the value:

@Bean
protected DefaultHttpClient httpClient(InjectionPoint<?> injectionPoint) {
    String url = metadata.stringValue(Client.class).orElse(null);
    if (url != null) {
        return new DefaultHttpClient(url);
    } else {
        return new DefaultHttpClient();
    }
}

Support for Eager Initialization of Beans

Eager initialization of beans is useful in certain cases, such as on AWS Lambda where more CPU resources are assigned to Lamdba construction than execution. Therefore as for Micronaut 2.0, you can specify whether you want to eager initialization configuration or all singletons using the ApplicationContextBuilder interface:

Enabling Eager Initialization

public class Application {

    public static void main(String[] args) {
        Micronaut.build(args)
            .eagerInitSingletons(true) <i data-value="1"></i><b>(1)</b>
            .mainClass(Application.class)
            .start();
    }
}
1 Setting eager init to true initializes all singletons

It is also possible to just eager init configuration using eagerInitConfiguration which will initialize all @ConfigurationProperties beans.

Spot Bugs Instead of JSR-305 Nullable/NonNull Annotations

In Micronaut 1.x the Google distributed JSR-305 annotations library ( com.google.code.findbugs:jsr305 ) was used to specify @Nullable and @NonNull on interfaces of the Micronaut API using the annotations contained within the javax.annotation package.

Due to the fact that JSR-305 has been cancelled and that this dependency has potential licensing issues (by using the javax namespace) as well as problems with the cross packages on Java 9+ with the module system Micronaut 2.x switches to the spotbugs-annotations module provided by the SpotBugs project .

It is recommended users of Micronaut use this API instead (although the javax.annotation.Nullable and javax.annotation.NotNull annotations continue to be supported).

CLI Features

New Native CLI

Micronaut’s mn command for the CLI has been rewritten in Micronaut itself and is now compiled into a native image available on Linux, MacOS X and Windows .

Micronaut Launch

Create Micronaut 2.0 applications without having the CLI installed using curl :

$ curl https://launch.micronaut.io/demo.zip -o demo.zip
$ unzip demo.zip -d demo

Or by visiting https://launch.micronaut.io in your browser.

Run curl https://launch.micronaut.io for more instructions on how to use the API or visit the OpenAPI documentation .

Diff Command

Run mn feature-diff --features=[FEATURE NAME] from the root of another Micronaut project to create a diff of the changes that need to be applied to enable the feature. For example:

Using feature-diff
$ mn feature-diff --features=azure-function
--- micronaut-cli.yml
+++ micronaut-cli.yml
@@ -3,4 +3,4 @@
 testFramework: junit
 sourceLanguage: java
 buildTool: gradle
-features: [app-name, application, gradle, http-client, java, junit, logback, netty-server, shade, yaml]
+features: [app-name, application, azure-function, azure-function-http, gradle, java, junit, logback, yaml]


--- host.json
+++ host.json
@@ -1,0 +1,7 @@
+{
+  "version": "2.0",
+  "extensionBundle": {
+    "id": "Microsoft.Azure.Functions.ExtensionBundle",
+    "version": "[1.*, 2.0.0)"
+  }
+}

GraalVM Improvements

Micronaut’s support for GraalVM Native Image has been moved out of experimental status, which solidifies our commitment to continue improving support for native images.

Automatic Static Resource Detection for Native Image

It is not longer necessary to configure static resources for your Native Immage builds. The micronaut-graal annotation processor will automatically do this for you for all resources found in src/main/resources .

Improved support for JDBC / Hibernate in Native Image

It is no longer necessary to provide additional GraalVM related configuration to connect to databases via JDBC or Hibernate/JPA. Micronaut includes automatic support for the following drivers with GraalVM Native Image:

  • Oracle

  • MariaDB

  • Postgres

  • MS SQL

  • H2

  • MySQL

Support for Flyway Migrations in Native Image

The Micronaut Flyway module has been updated with GraalVM Native Image support so you can now run database migrations in Native Image.

Support for Native Image in AWS SDK v2

Version 2.0 of the Micronaut AWS module includes support for Native Image for the majority of the v2 AWS APIs including S3, Dynamo DB, SES, SNS, and SQS which will be helpful for those developing native AWS Lambda functions with Micronaut + GraalVM.

Support for jOOQ in Native Image

The Micronaut jOOQ module includes support for Native Image and it’s possible to use it with SimpleFlatMapper .

Support for Redis in Native Image

The Micronaut Redis module includes support for Native Image . There are still some pending uses cases that won’t work because of how Lettuce driver works. Make sure you read the documentation.

Support for Elasticsearch in Native Image

The Micronaut Elasticsearch module includes support for Native Image

Build Improvements

New Maven Parent POM

Micronaut now provides a new parent POM that can be used in Maven projects to get setup quickly:

Using the Maven Parent POM

<parent>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-parent</artifactId>
    <version>${micronaut.version}</version>
</parent>

New Maven Plugin

The parent POM mentioned above includes a new Micronaut Maven Plugin that enables automatic application restart during development. Just run the following:

$ ./mvnw mn:run

Whenever you make a change to a class file the server will restart automatically.

Gradle 6.5 Update

For Gradle users who create new applications Gradle 6.5 is used which is compatible with JDK 14.

Better Gradle Incremental Annotation Processing Support

Gradle builds with Micronaut 2 for both Java and Kotlin should be significantly faster thanks to improved support for Gradle incremental annotation processing .

HTTP Features

Support for HTTP/2

Micronaut’s Netty-based HTTP client and server have been updated to support HTTP/2.

See thefor more information on how to enable support for HTTP/2.

Threading Model and Event Loop Group Improvements

Micronaut 2.0 uses a new shared default Netty EventLoopGroup for server worker threads and client request threads. This reduces context switching and improves resource utilization.

See theHTTP Client Configurationsection for information on how to configure the default EventLoopGroup and add additional `EventLoopGroup’s that are configured per client.

In addition, as of Micronaut 2.0 all operations are by default executed on the EventLoop and users can optionally use the new@ExecuteOn annotation to specify a named executor to execute an operation on if required (for example to offload blocking operations such as interactions with JPA/JDBC to a specific thread pool).

Support for @RequestBean

It is now possible to bind the properties of a POJO argument to a @Controller to request parameters, headers and so on using the@RequestBean annotation.

Thanks to Github user asodja for this contribution.

Micronaut Servlet

Micronaut now includes support for creating Servlet applications and users can use the command line to create an application that targets popular Servlet containers:

$ mn create-app myapp --features jetty-server    # for Jetty
$ mn create-app myapp --features tomcat-server   # for Tomcat
$ mn create-app myapp --features undertow-server # for Undertow

Improved Support for Server-Side Content Negotiation

Micronaut will now correctly handle the HTTP Accept header and pick the most appropriate route for the specified accepted media types using Server-Side Content Negotiation .

This also applies to @Error routes making it possible to send different error responses for different content types
To add XML support use the Jackson XML module

Improved Support for Cloud Foundry

Micronaut will now process the VCAP_APPLICATION and VCAP_SERVICES environment variables and treat them as property sources.

Thanks to Fabian Nonnenmacher for this contribution.

HTTP Client Improvements

It is no longer necessary to use @Client(..) to inject a defaultRxHttpClient instance. You can now inject the default client simply with:

@Inject RxHttpClient client;

If no host is provided at the time of a request, aNoHostException will be thrown.

API for Proxying Requests

A new API for writing API gateways and proxying requests has been added. See the documentation on thefor more information.

Endpoint Sensitivity

It is now possible to control the sensitivity of individual endpoint methods. The@Sensitive annotation can be applied to endpoint methods to allow for some methods to have a different sensitivity than the value supplied to the endpoint annotation.

Improvements to Instrumentation

The Instrumentation mechanism for RxJava 2 has been improved to address issues with MDC and reduce the size of reactive stack traces. Thanks to Denis Stepanov and Lajos Gathy for their contributions in this area.

Kotlin Improvements

Support for KTOR in Micronaut Launch

You can generate a Micronaut + Ktor application fromMicronaut Launch or via the command line.

Micronaut Kotlin Extensions

New Kotlin Extension Functions are available that make the Kotlin + Micronaut experience that little bit better.

Serverless Improvements

Support for Google Cloud Function

You can now write Serverless functions that target Google Cloud Function using Micronaut. See the Micronaut GCP documentation and example application for more information.

Support for Microsoft Azure Function

You can now write Serverless functions that target Microsoft Azure using Micronaut. See the Micronaut Azure documentation and example application for more information.

Improvements to Micronaut AWS

Micronaut AWS 2.0.0 includes a number of improvements to support for AWS Lambda and AWS in general including new client modules for AWS SDK 2.0, cold start improvements on Lambda and improvements to the support for Amazon Alexa.

Module Improvements

Micronaut is more modular than ever, with several components now available in separate modules and upgrades to those modules.

Micronaut Cache 2.0.0 Upgrade

Caching has been moved into a separate module and out of micronaut-runtime . If you need caching (including the annotations within io.micronaut.cache.annotation ) you just need to add the individual module for the cache provider you are interested (for example Caffeine, Redis, Hazelcast etc.).

See the documentation for the Cache module for more information.

Micronaut SQL 2.3.0 Upgrade

Micronaut SQL has been improved to default to Micronaut transaction management (making Spring management optional) and includes support for Jdbi (Thanks to Dan Maas for this contribution).

In addition, support has been added for Oracle Universal Connection Pool . Thanks to Todd Sharp for this contribution.

Micronaut Security 2.0.0 Upgrade

The security module has seen many changes to improve the API and introduce new features to support a wider array of use cases.

See the Security module for more information.

New Reactive Modules

Whilst RxJava 2 remains the default, individual modules for other reactive libraries have been added.

For RxJava 3:

implementation("io.micronaut.rxjava3:micronaut-rxjava3")
<dependency>
    <groupId>io.micronaut.rxjava3</groupId>
    <artifactId>micronaut-rxjava3</artifactId>
</dependency>

For Reactor:

implementation("io.micronaut.reactor:micronaut-reactor")
<dependency>
    <groupId>io.micronaut.reactor</groupId>
    <artifactId>micronaut-reactor</artifactId>
</dependency>

And legacy support for RxJava 1:

implementation("io.micronaut.rxjava1:micronaut-rxjava1")
<dependency>
    <groupId>io.micronaut.rxjava1</groupId>
    <artifactId>micronaut-rxjava1</artifactId>
</dependency>

Included within the new RxJava 3 and Reactor modules are variants ofRxHttpClient called Rx3HttpClient and ReactorHttpClient respectively.

To use the RxJava 3 HTTP client add the following dependency:

implementation("io.micronaut.rxjava3:micronaut-rxjava3-http-client")
<dependency>
    <groupId>io.micronaut.rxjava3</groupId>
    <artifactId>micronaut-rxjava3-http-client</artifactId>
</dependency>

To use the Reactor HTTP client add:

implementation("io.micronaut.rxjava3:micronaut-reactor-http-client")
<dependency>
    <groupId>io.micronaut.rxjava3</groupId>
    <artifactId>micronaut-reactor-http-client</artifactId>
</dependency>

New Micronaut NATS module

A new messaging module for Nats.io has been included in Micronaut core.

See the documentation for Micronaut Nats for more information.

Thanks to Joachim Grimm for this contribution.

Module Upgrades

  • Micronaut AWS - 1.3.92.0.0.RC1

  • Micronaut Cache - 1.2.02.0.0.RC1

  • Micronaut Data - 1.0.21.1.0.RC2

  • Micronaut GCP - 1.1.02.0.0.RC2

  • Micronaut gRPC - 1.1.12.0.0.RC1

  • Micronaut Micrometer - 1.3.12.0.0.RC2

  • Micronaut Mongo - 1.3.02.1.0

  • Micronaut Neo4j - 1.3.03.0.0.RC1

  • Micronaut SQL - 1.3.02.3.0

  • Micronaut Security - 1.4.02.0.0.RC1

  • Micronaut Spring - 1.0.22.0.1

Dependency Upgrades

  • Hibernate 5.4.10.Final5.4.16.Final

  • Groovy 2.5.83.0.3

  • Mongo Reactive Streams 1.13.04.0.2

  • Mongo Java Driver 3.12.04.0.2

  • Jaeger 1.0.01.2.0

  • Jackson 2.10.32.11.0

1.2 Upgrading to Micronaut 2.x

This section covers the steps required to upgrade a Micronaut 1.x application to Micronaut 2.x.

Thread Pool Selection Now Manual

If your application does blocking I/O such as communication via JDBC or JPA you should annotate all controllers that interact with these blocking resources with the@ExecuteOn annotation specifying either the default I/O thread pool or a thread pool that you configure an manage to deal with these blocking interactions. For example:

Using @ExecuteOn

import io.micronaut.http.annotation.*;
import io.micronaut.scheduling.TaskExecutors;
import io.micronaut.scheduling.annotation.ExecuteOn;

@Controller("/executeOn/people")
public class PersonController {

    private final PersonService personService;

    PersonController(
            PersonService personService) {
        this.personService = personService;
    }

    @Get("/{name}")
    @ExecuteOn(TaskExecutors.IO) <b>(1)</b>
    Person byName(String name) {
        return personService.findByName(name);
    }
}

Using @ExecuteOn

import io.micronaut.http.annotation.*
import io.micronaut.scheduling.TaskExecutors
import io.micronaut.scheduling.annotation.ExecuteOn

@Controller("/executeOn/people")
class PersonController {

    private final PersonService personService

    PersonController(
            PersonService personService) {
        this.personService = personService
    }

    @Get("/{name}")
    @ExecuteOn(TaskExecutors.IO) <b>(1)</b>
    Person byName(String name) {
        return personService.findByName(name)
    }
}

Using @ExecuteOn

import io.micronaut.http.annotation.*
import io.micronaut.scheduling.TaskExecutors
import io.micronaut.scheduling.annotation.ExecuteOn

@Controller("/executeOn/people")
class PersonController (private val personService: PersonService) {
    @Get("/{name}")
    @ExecuteOn(TaskExecutors.IO) <b>(1)</b>
    fun byName(name: String): Person {
        return personService.findByName(name)
    }
}
1 The@ExecuteOn annotation is used to execute the operation on the I/O thread pool
If you wish to return to the previous behaviour as defined in Micronaut 1.x you can set micronaut.server.thread-selection to AUTO in configuration.

Compilation Error with classes in io.micronaut.cache package

If your application fails to compile due to missing classes in the io.micronaut.cache you need to add a dependency on a cache implementation, for example the default cache implementation was Caffeine and this can be restored with the following dependency:

implementation("io.micronaut.cache:micronaut-cache-caffeine")
<dependency>
    <groupId>io.micronaut.cache</groupId>
    <artifactId>micronaut-cache-caffeine</artifactId>
</dependency>

Compilation Error with classes in javax.annotation package

If you application fails to compile referencing classes in the javax.annotation package such as @Nullable and @Nonnull you should update your project to instead import edu.umd.cs.findbugs.annotations from Spot Bugs.

New Group IDs

Some dependencies have new Maven Group IDs so you may need to update your dependency. The following table summarizes changes to group IDs:

Table 1. Updated Maven Group IDs
Previous ID New ID

io.micronaut.configuration:micronaut-aws-*

io.micronaut.aws:micronaut-aws-*

io.micronaut.configuration:micronaut-cassandra

io.micronaut.cassandra:micronaut-cassandra

io.micronaut.configuration:micronaut-elasticsearch

io.micronaut.elasticsearch:micronaut-elasticsearch

io.micronaut.configuration:micronaut-flyway

io.micronaut.flyway:micronaut-flyway

io.micronaut.configuration:micronaut-runtime-groovy

io.micronaut.groovy:micronaut-runtime-groovy

io.micronaut.configuration:micronaut-hibernate-validator

io.micronaut.beanvalidation:micronaut-hibernate-validator

io.micronaut.configuration:micronaut-jmx

io.micronaut.jmx:micronaut-jmx

io.micronaut.configuration:micronaut-kafka

io.micronaut.kafka:micronaut-kafka

io.micronaut.configuration:micronaut-liquibase

io.micronaut.liquibase:micronaut-liquibase

io.micronaut.configuration:micronaut-micrometer-*

io.micronaut.micrometer:micronaut-micrometer-*

io.micronaut.configuration:micronaut-mongo

io.micronaut.mongodb:micronaut-mongo-reactive

io.micronaut.configuration:micronaut-neo4j

io.micronaut.neo4j:micronaut-neo4j

io.micronaut.configuration:micronaut-netflix-*

io.micronaut.netflix:micronaut-netflix-*

io.micronaut.configuration:micronaut-picocli

io.micronaut.picocli:micronaut-picocli

io.micronaut.configuration:micronaut-rabbitmq

io.micronaut.rabbitmq:micronaut-rabbitmq

io.micronaut.configuration:micronaut-redis

io.micronaut.redis:micronaut-redis

io.micronaut.configuration:micronaut-rss

io.micronaut.rss:micronaut-rss

io.micronaut.configuration:micronaut-itunespodcast

io.micronaut.rss:micronaut-itunespodcast

io.micronaut:micronaut-security-*

io.micronaut.security:micronaut-security-*

io.micronaut.configuration:micronaut-views-*

io.micronaut.views:micronaut-views-*

AWS Module Changes

The AWS functionality (including support for AWS ParameterStore, AWS Route53 and AWS Lambda Function Client) has been moved out of the Micronaut core therefore if you need AWS related functionality you should use the necessary module.

Table 2. Updated AWS Modules
Package New Dependency

io.micronaut.function.client.aws

io.micronaut.aws:micronaut-function-client-aws

io.micronaut.discovery.aws.parameterstore

io.micronaut.aws:micronaut-aws-parameter-store

io.micronaut.discovery.aws.route53

io.micronaut.aws:micronaut-aws-route53

Kubernetes Discovery Service deprecation

The class io.micronaut.discovery.kubernetes.KubernetesDiscoveryClient , that was deprecated in Micronaut 1.2, has now been removed from Micronaut 2.0. The replacement is the new Micronaut Kubernetes module, with an improved support for running Micronaut applications in a Kubernetes cluster, including support for Kubernetes' ConfigMap s, Secret s and more.

To use the new module, you need to add the following dependency:

implementation("io.micronaut.kubernetes:micronaut-kubernetes-discovery-client")
<dependency>
    <groupId>io.micronaut.kubernetes</groupId>
    <artifactId>micronaut-kubernetes-discovery-client</artifactId>
</dependency>

Transaction Management

For Micronaut 2.0, we recommend you to use the native Micronaut-based transaction management instead of other alternatives such as Spring Transaction Management.

You will use Java EE 7 javax.transaction.Transactional annotation and io.micronaut.transaction.annotation.ReadOnly to mark your transaction demarcations.

To use those annotations, you have to include the micronaut-data-processor dependency in your annotation processor configuration:

annotationProcessor("io.micronaut.data:micronaut-data-processor")
<dependency>
    <groupId>io.micronaut.data</groupId>
    <artifactId>micronaut-data-processor</artifactId>
</dependency>

If you use Hibernate/JPA, you will need to add as well:

implementation("io.micronaut.data:micronaut-data-hibernate-jpa")
<dependency>
    <groupId>io.micronaut.data</groupId>
    <artifactId>micronaut-data-hibernate-jpa</artifactId>
</dependency>

Then, code such as:

import io.micronaut.spring.tx.annotation.Transactional;
...
..
.
@Singleton
public class GenreRepositoryImpl implements GenreRepository {
...
..
.
    @Transactional(readOnly = true)
    public Optional<Genre> findById(@NotNull Long id) {
...
..
    }

    @Transactional
    public Genre save(@NotBlank String name) {
    ...
    ..
    }
}

becomes:

import javax.transaction.Transactional;
import io.micronaut.transaction.annotation.ReadOnly;
...
..
.
@Singleton
public class GenreRepositoryImpl implements GenreRepository {
...
..
.
    @ReadOnly
    public Optional<Genre> findById(@NotNull Long id) {
...
..
    }

    @Transactional
    public Genre save(@NotBlank String name) {
    ...
    ..
    }
}

Micronaut 2 for Groovy Users

Micronaut 2 defaults to Groovy 3 and Spock 2 both of which include significant changes at the language and testing framework level.

Some features of Micronaut 2 for Groovy users are currently in preview status including GORM 7.1 and Spock 2 support as these frameworks do not yet have stable releases.

In the case of Spock 2 the most important change is that Spock 2 deprecates support for JUnit 4 and the associated JUnit 4 test runner and replaces it with JUnit 5 Platform.

In the case of a Gradle build this change means that when upgrading to Micronaut 2 with Spock 2 you may find your tests don’t execute at all which can give you the false sense of security that the upgrade was successful.

To ensure your Spock 2 tests run in a Micronaut 2 Gradle build you must add the following configuration to your build.gradle to enable JUnit 5 platform:

Using JUnit Platform

test {
  useJUnitPlatform()
}

With this configuration is place your Spock 2 tests will execute correctly.

Other Breaking Changes

If the above cases don’t cover your use case see the section onfor a list of other changes that are regarded as breaking in this release.

2 Quick Start

The following sections will walk you through a Quick start on how to use Micronaut to setup a basic "Hello World" application.

Before getting started ensure you have a Java 8 or above SDK installed and it is recommended having a suitable IDE such as IntelliJ IDEA.

To follow the Quick Start it is also recommended that you have the Micronaut CLI installed.

2.1 Build/Install the CLI

The best way to install Micronaut on Unix systems is with SDKMAN which greatly simplifies installing and managing multiple Micronaut versions.

2.1.1 Install with Sdkman

Before updating make sure you have latest version of SDKMAN installed. If not, run

$ sdk update

In order to install Micronaut, run following command:

$ sdk install micronaut

You can also specify the version to the sdk install command.

$ sdk install micronaut 2.0.0

You can find more information about SDKMAN usage on the SDKMAN Docs

You should now be able to run the Micronaut CLI.

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

2.1.2 Install with Homebrew

Before installing make sure you have latest Homebrew updates.

$ brew update

In order to install Micronaut, run following command:

$ brew install micronaut

You can find more information about Homebrew usage on their homepage .

You should now be able to run the Micronaut CLI.

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

2.1.3 Install with MacPorts

Before installing it is recommended to sync the latest Portfiles.

$ sudo port sync

In order to install Micronaut, run following command:

$ sudo port install micronaut

You can find more information about MacPorts usage on their homepage .

You should now be able to run the Micronaut CLI.

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

2.1.4 Install through Binary on Windows

  • Download the latest binary fromMicronaut Website

  • Extract the binary to appropriate location (For example: C:\micronaut )

  • Create an environment variable MICRONAUT_HOME which points to the installation directory i.e. C:\micronaut

  • Update the PATH environment variable, append %MICRONAUT_HOME%\bin .

You should now be able to run the Micronaut CLI from the command prompt as follows:

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

2.1.5 Building from Source

Clone the repository as follows:

$ git clone https://github.com/micronaut-projects/micronaut-core.git

cd into the micronaut-core directory and run the following command:

$ ./gradlew cli:fatJar

This created the fatJar for use in the CLI.

In your shell profile ( ~/.bash_profile if you are using the Bash shell), export the MICRONAUT_HOME directory and add the CLI path to your PATH :

bash_profile/.bashrc

export MICRONAUT_HOME=~/path/to/micronaut-core
export PATH="$PATH:$MICRONAUT_HOME/cli/build/bin"

Reload your terminal or source your shell profile with source :

> source ~/.bash_profile

You are now be able to run the Micronaut CLI.

$ mn
| Starting interactive mode...
| Enter a command name to run. Use TAB for completion:
mn>
You can also point SDKMAN to local installation for dev purpose using following command sdk install micronaut dev /path/to/checkout/cli/build

2.2 Creating a Server Application

Although not required to use Micronaut, the Micronaut CLI is the quickest way to create a new server application.

Using the CLI you can create a new Micronaut application in either Groovy, Java or Kotlin (the default is Java).

The following command creates a new "Hello World" server application in Java with a Gradle build:

$ mn create-app hello-world
You can supply --build maven if you wish to create a Maven based build instead

The previous command will create a new Java application in a directory called hello-world featuring a Gradle build. The application can be run with ./gradlew run :

$ ./gradlew run
> Task :run
[main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 972ms. Server Running: http://localhost:28933

If you have created a Maven based project, use ./mvnw mn:run instead.

For Windows the ./ before commands is not needed

By default the Micronaut HTTP server is configured to run on port 8080. See the sectionRunning Server on a Specific Portin the user guide for more options.

In order to create a service that responds to "Hello World" you first need a controller. The following is an example of a controller:

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

@Controller("/hello") <b>(1)</b>
public class HelloController {

    @Get(produces = MediaType.TEXT_PLAIN) <b>(2)</b>
    public String index() {
        return "Hello World"; <b>(3)</b>
    }
}
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get

@Controller('/hello') <b>(1)</b>
class HelloController {

    @Get(produces = MediaType.TEXT_PLAIN) <b>(2)</b>
    String index() {
        'Hello World' <b>(3)</b>
    }
}
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get

@Controller("/hello") <b>(1)</b>
class HelloController {

    @Get(produces = [MediaType.TEXT_PLAIN]) <b>(2)</b>
    fun index(): String {
        return "Hello World" <b>(3)</b>
    }
}
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 A String "Hello World" is returned as the result

If you are using Java, place the previous file in src/main/java/hello/world .

If you are using Groovy, place the previous file in src/main/groovy/hello/world .

If you are using Kotlin, place the previous file in src/main/kotlin/hello/world .

If you start the application and send a request to the /hello URI then the text "Hello World" is returned:

$ curl http://localhost:8080/hello
Hello World

2.3 Setting up an IDE

The application created in the previous section contains a "main class" located in src/main/java that looks like the following:

import io.micronaut.runtime.Micronaut;

public class Application {

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

class Application {

    static void main(String... args) {
        Micronaut.run Application.class
    }
}
import io.micronaut.runtime.Micronaut

object Application {

    @JvmStatic
    fun main(args: Array<String>) {
        Micronaut.run(Application.javaClass)
    }
}

This is the class that is run 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.

Configuring IntelliJ IDEA

To import a Micronaut project into IntelliJ IDEA simply open the build.gradle or pom.xml file and follow the instructions to import the project.

For IntelliJ IDEA if you plan to use the IntelliJ compiler then you should enable annotation processing under the "Build, Execution, Deployment → Compiler → Annotation Processors" by ticking the "Enable annotation processing" checkbox:

Micronaut 2.0: a full stack Java framework for modular, testable applications

Once you have enabled annotation processing in IntelliJ you can run the application and tests directly within the IDE without the need of an external build tool such as Gradle or Maven.

Configuring Eclipse IDE

If you wish to use Eclipse IDE, it is recommended you import your Micronaut project into Eclipse using either Gradle BuildShip for Gradle or M2Eclipse for Maven.

Micronaut requires Eclipse IDE 4.9 or above

Eclipse and Gradle

Once you have setup Eclipse 4.9 or above with Gradle BuildShip first run the gradle eclipse task from the root of your project then import the project by selecting File → Import then choosing Gradle → Existing Gradle Project and navigating to the root directory of your project (where the build.gradle is located).

Eclipse and Maven

For Eclipse 4.9 and above with Maven you need the following Eclipse plugins:

Once installed you need to import the project by selecting File → Import then choosing Maven → Existing Maven Project and navigating to the root directory of your project (where the pom.xml is located).

You should then enable annotation processing by opening Eclipse → Preferences and navigating to Maven → Annotation Processing and selecting the option Automatically configure JDT APT .

Configuring Visual Studio Code

Micronaut can be setup within Visual Studio Code. You will need to first install the The Java Extension Pack .

You can also optionally install STS to enable code completion for application.yml .

If you are using Gradle prior to opening the project in VSC you should run the following command from a terminal window:

./gradlew eclipse

Once the extension pack is installed and if you have setup terminal integration just type code . in any project directory and the project will be automatically setup.

2.4 Creating a Client

As mentioned previously, Micronaut includes both anand an. Alow-level HTTP clientis provided out of the box which you can use to test the HelloController created in the previous section.

import io.micronaut.context.annotation.Property;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.runtime.server.EmbeddedServer;
import io.micronaut.test.annotation.MicronautTest;
import org.junit.jupiter.api.Test;

import javax.inject.Inject;

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

@MicronautTest
public class HelloControllerSpec {
    @Inject
    EmbeddedServer server; <b>(1)</b>

    @Inject
    @Client("/")
    HttpClient client; <b>(2)</b>

    @Test
    void testHelloWorldResponse() {
        String response = client.toBlocking() <b>(3)</b>
                .retrieve(HttpRequest.GET("/hello"));
        assertEquals("Hello World", response); //) <b>(4)</b>
    }
}
import io.micronaut.http.HttpRequest
import io.micronaut.http.client.HttpClient
import io.micronaut.http.client.annotation.Client
import io.micronaut.runtime.server.EmbeddedServer
import io.micronaut.test.annotation.MicronautTest
import spock.lang.Specification
import javax.inject.Inject

@MicronautTest
class HelloControllerSpec extends Specification {
    @Inject
    EmbeddedServer embeddedServer <b>(1)</b>

    @Inject
    @Client("/")
    HttpClient client <b>(2)</b>

    void "test hello world response"() {
        expect:
            client.toBlocking() <b>(3)</b>
                    .retrieve(HttpRequest.GET('/hello')) == "Hello World" <b>(4)</b>
    }
}
import io.micronaut.context.annotation.Property
import io.micronaut.http.client.HttpClient
import io.micronaut.http.client.annotation.Client
import io.micronaut.runtime.server.EmbeddedServer
import io.micronaut.test.annotation.MicronautTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import javax.inject.Inject

@MicronautTest
class HelloControllerSpec {

    @Inject
    lateinit var server: EmbeddedServer <b>(1)</b>

    @Inject
    @field:Client("/")
    lateinit var client: HttpClient <b>(2)</b>

    @Test
    fun testHelloWorldResponse() {
        val rsp: String = client.toBlocking() <b>(3)</b>
                .retrieve("/hello")
        assertEquals("Hello World", rsp) <b>(4)</b>
    }
}
1 TheEmbeddedServer is configured as a shared test field
2 AHttpClient instance shared field is also defined
3 The test using the toBlocking() method to make a blocking call
4 The retrieve method returns the response of the controller as a String

In addition to a low-level client, Micronaut features adeclarative, compile-time HTTP client, powered by theClient annotation.

To create a client, simply create an interface annotated with @Client . For example:

import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.annotation.Client;
import io.reactivex.Single;

@Client("/hello") <b>(1)</b>
public interface HelloClient {

    @Get(consumes = MediaType.TEXT_PLAIN) <b>(2)</b>
    Single<String> hello(); <b>(3)</b>
}
import io.micronaut.http.annotation.Get
import io.micronaut.http.client.annotation.Client
import io.reactivex.Single

@Client("/hello") <b>(1)</b>
interface HelloClient {

    @Get(consumes = MediaType.TEXT_PLAIN) <b>(2)</b>
    Single<String> hello() <b>(3)</b>
}
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Get
import io.micronaut.http.client.annotation.Client
import io.reactivex.Single

@Client("/hello") <b>(1)</b>
interface HelloClient {

    @Get(consumes = [MediaType.TEXT_PLAIN]) <b>(2)</b>
    fun hello(): Single<String>  <b>(3)</b>
}
1 The @Client annotation is used with value that is a relative path to the current server
2 The same@Get annotation used on the server is used to define the client mapping
3 A RxJava Single is returned with the value read from the server

To test the HelloClient simply retrieve it from theApplicationContext associated with the server:

import io.micronaut.test.annotation.MicronautTest;
import javax.inject.Inject;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

@MicronautTest <b>(1)</b>
public class HelloClientSpec  {

    @Inject
    HelloClient client; <b>(2)</b>

    @Test
    public void testHelloWorldResponse(){
        assertEquals("Hello World", client.hello().blockingGet());<b>(3)</b>
    }
}
import io.micronaut.test.annotation.MicronautTest
import spock.lang.Specification

import javax.inject.Inject

@MicronautTest <b>(1)</b>
class HelloClientSpec extends Specification {

    @Inject HelloClient client <b>(2)</b>


    void "test hello world response"() {
        expect:
        client.hello().blockingGet() == "Hello World" <b>(3)</b>
    }

}
import io.micronaut.context.annotation.Property
import io.micronaut.test.annotation.MicronautTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import javax.inject.Inject

@MicronautTest <b>(1)</b>
class HelloClientSpec {

    @Inject
    lateinit var client: HelloClient <b>(2)</b>

    @Test
    fun testHelloWorldResponse() {
        assertEquals("Hello World", client.hello().blockingGet())<b>(3)</b>
    }
}
1 The @MicronautTest annotation is used to define the test
2 The HelloClient is injected from theApplicationContext
3 The client is invoked using RxJava’s blockingGet method

TheClient annotation produces an implementation automatically for you at compile time without the need to use proxies or runtime reflection.

TheClient annotation is very flexible. See the section on theMicronaut HTTP Clientfor more information.

2.5 Deploying the Application

To deploy a Micronaut application you create a runnable JAR file by running ./gradlew assemble or ./mvnw package .

The constructed JAR file can then be executed with java -jar . For example:

$ java -jar build/libs/hello-world-all.jar

The runnable JAR can also easily be packaged within a Docker container or deployed to any Cloud infrastructure that supports runnable JAR files.

3 Inversion of Control

When most developers think of Inversion of Control (also known as Dependency Injection and referred to as such from this point onwards) the Spring Framework comes to mind.

Micronaut takes inspiration from Spring, and in fact, the core developers of Micronaut are former SpringSource/Pivotal engineers now working for OCI .

Unlike Spring which relies exclusively on runtime reflection and proxies, Micronaut uses compile time data to implement dependency injection.

This is a similar approach taken by tools such as Google’s Dagger , which is designed primarily with Android in mind. Micronaut, on the other hand, is designed for building server-side microservices and provides many of the same tools and utilities as Spring but without using reflection or caching excessive amounts of reflection metadata.

The goals of the Micronaut IoC container are summarized as:

  • Use reflection as a last resort

  • Avoid proxies

  • Optimize start-up time

  • Reduce memory footprint

  • Provide clear, understandable error handling

Note that the IoC part of Micronaut can be used completely independently of Micronaut itself for whatever application type you may wish to build. To do so all you need to do is configure your build appropriately to include the micronaut-inject-java dependency as an annotation processor. For example with Gradle:

Configuring Gradle

plugins {
  id "net.ltgt.apt" version "0.21" // <1>
}

...
dependencies {
    annotationProcessor("io.micronaut:micronaut-inject-java:2.0.0") // <2>
    implementation("io.micronaut:micronaut-inject:2.0.0")
    ...
    testAnnotationProcessor("io.micronaut:micronaut-inject-java:2.0.0") // <3>
    ...
}
1 Apply net.ltgt.apt Gradle plugin which makes it easier/safer to use Java annotation processors
2 Include the minimal dependencies required to perform dependency injection
3 This is necessary to create beans in the test directory
For the Groovy language you should include micronaut-inject-groovy in the compileOnly and testCompileOnly scopes.

The entry point for IoC is then theApplicationContext interface, which includes a run method. The following example demonstrates using it:

Running the ApplicationContext
try (ApplicationContext context = ApplicationContext.run()) { <i data-value="1"></i><b>(1)</b>
    MyBean myBean = context.getBean(MyBean.class); <i data-value="2"></i><b>(2)</b>
    // do something with your bean
}
1 Run theApplicationContext
2 Retrieve a bean that has been dependency injected
The example uses Java’s try-with-resources syntax to ensure theApplicationContext is cleanly shutdown when the application exits.

3.1 Defining Beans

A bean is an object that has its lifecycle controlled by the Micronaut IoC container. That lifecycle may include creation, execution, and destruction. Micronaut implements the JSR-330 (javax.inject) - Dependency Injection for Java specification hence to use Micronaut you simply use the annotations provided by javax.inject .

The following is a simple example:

public interface Engine { <b>(1)</b>
    int getCylinders();
    String start();
}

@Singleton<b>(2)</b>
public class V8Engine implements Engine {
    public String start() {
        return "Starting V8";
    }

    public int getCylinders() {
        return cylinders;
    }

    public void setCylinders(int cylinders) {
        this.cylinders = cylinders;
    }

    private int cylinders = 8;
}

@Singleton
public class Vehicle {
    private final Engine engine;

    public Vehicle(Engine engine) {<b>(3)</b>
        this.engine = engine;
    }

    public String start() {
        return engine.start();
    }
}
interface Engine { <b>(1)</b>
    int getCylinders()
    String start()
}

@Singleton <b>(2)</b>
class V8Engine implements Engine {
    int cylinders = 8

    String start() {
        "Starting V8"
    }
}

@Singleton
class Vehicle {
    final Engine engine

    Vehicle(Engine engine) { <b>(3)</b>
        this.engine = engine
    }

    String start() {
        engine.start()
    }
}
interface Engine {
    <b>(1)</b>
    val cylinders: Int

    fun start(): String
}

@Singleton<b>(2)</b>
class V8Engine : Engine {

    override var cylinders = 8
    override fun start(): String {
        return "Starting V8"
    }
}

@Singleton
class Vehicle(private val engine: Engine)<b>(3)</b>
{

    fun start(): String {
        return engine.start()
    }
}
1 A common Engine interface is defined
2 A V8Engine implementation is defined and marked with Singleton scope
3 The Engine is injected via constructor injection

To perform dependency injection simply run theBeanContext using the run() method and lookup a bean using getBean(Class) , as per the following example:

final BeanContext context = BeanContext.run();
Vehicle vehicle = context.getBean(Vehicle.class);
System.out.println(vehicle.start());
def context = BeanContext.run()
Vehicle vehicle = context.getBean(Vehicle)
println( vehicle.start() )
val context = BeanContext.run()
val vehicle = context.getBean(Vehicle::class.java)
println(vehicle.start())

Micronaut will automatically discover dependency injection metadata on the classpath and wire the beans together according to injection points you define.

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

3.2 How Does it Work?

At this point, you may be wondering how Micronaut performs the above dependency injection without requiring reflection.

The key is a set of AST transformations (for Groovy) and annotation processors (for Java) that generate classes that implement theBeanDefinition interface.

The ASM byte-code library is used to generate classes and because Micronaut knows ahead of time the injection points, there is no need to scan all of the methods, fields, constructors, etc. at runtime like other frameworks such as Spring do.

Also since reflection is not used in the construction of the bean, the JVM can inline and optimize the code far better resulting in better runtime performance and reduced memory consumption. This is particularly important for non-singleton scopes where the application performance depends on bean creation performance.

In addition, with Micronaut your application startup time and memory consumption is not bound to the size of your codebase in the same way as a framework that uses reflection. Reflection based IoC frameworks load and cache reflection data for every single field, method, and constructor in your code. Thus as your code grows in size so do your memory requirements, whilst with Micronaut this is not the case.

3.3 The BeanContext

TheBeanContext is a container object for all your bean definitions (it also implements BeanDefinitionRegistry ).

It is also the point of initialization for Micronaut. Generally speaking however, you don’t have to interact directly with the BeanContext API and can simply use javax.inject annotations and the annotations defined within io.micronaut.context.annotation package for your dependency injection needs.

3.4 Injectable Container Types

In addition to being able to inject beans Micronaut natively supports injecting the following types:

Table 1. Injectable Container Types
Type Description Example

java.util.Optional

An Optional of a bean. If the bean doesn’t exist empty() is injected

Optional<Engine>

java.lang.Iterable

An Iterable or subtype of Iterable (example List , Collection etc.)

Iterable<Engine>

java.util.stream.Stream

A lazy Stream of beans

Stream<Engine>

Array

A native array of beans of a given type

Engine[]

Provider

A javax.inject.Provider if a circular dependency requires it or to instantiate a prototype for each get call.

Provider<Engine>

A prototype bean will have one instance created per place the bean is injected. When a prototype bean is injected as a Provider , each call to get() will create a new instance.

3.5 Bean Qualifiers

If you have multiple possible implementations for a given interface that you want to inject, you need to use a qualifier.

Once again Micronaut leverages JSR-330 and the Qualifier and Named annotations to support this use case.

Qualifying By Name

To qualify by name you can use the Named annotation. For example, consider the following classes:

public interface Engine { <b>(1)</b>
    int getCylinders();
    String start();
}

@Singleton
public class V6Engine implements Engine {  <b>(2)</b>
    public String start() {
        return "Starting V6";
    }

    public int getCylinders() {
        return 6;
    }
}

@Singleton
public class V8Engine implements Engine {
    public String start() {
        return "Starting V8";
    }

    public int getCylinders() {
        return 8;
    }

}

@Singleton
public class Vehicle {
    private final Engine engine;

    @Inject
    public Vehicle(@Named("v8") Engine engine) {<b>(4)</b>
        this.engine = engine;
    }

    public String start() {
        return engine.start();<b>(5)</b>
    }
}
interface Engine { <b>(1)</b>
    int getCylinders()
    String start()
}

@Singleton
class V6Engine implements Engine { <b>(2)</b>
    int cylinders = 6

    String start() {
        "Starting V6"
    }
}

@Singleton
class V8Engine implements Engine { <b>(3)</b>
    int cylinders = 8

    String start() {
        "Starting V8"
    }
}

@Singleton
class Vehicle {
    final Engine engine

    @Inject Vehicle(@Named('v8') Engine engine) { <b>(4)</b>
        this.engine = engine
    }

    String start() {
        engine.start() <b>(5)</b>
    }
}
interface Engine { <b>(1)</b>
    val cylinders: Int
    fun start(): String
}

@Singleton
class V6Engine : Engine {  <b>(2)</b>

    override var cylinders: Int = 6

    override fun start(): String {
        return "Starting V6"
    }
}

@Singleton
class V8Engine : Engine {

    override var cylinders: Int = 8

    override fun start(): String {
        return "Starting V8"
    }

}

@Singleton
class Vehicle @Inject
constructor(@param:Named("v8") private val engine: Engine)<b>(4)</b>
{

    fun start(): String {
        return engine.start()<b>(5)</b>
    }
}
1 The Engine interface defines the common contract
2 The V6Engine class is the first implementation
3 The V8Engine class is the second implementation
4 The javax.inject.Named annotation is used to indicate the V8Engine implementation is required
5 Calling the start method prints: "Starting V8"

Micronaut is capable of injecting V8Engine in the previous example, because:

@Named qualifier value ( v8 ) + type being injected simple name ( Engine ) == (case insensitive) == The simple name of a bean of type Engine ( V8Engine )

You can also declare @Named at the class level of a bean to explicitly define the name of the bean.

Qualifying By Annotation

In addition to being able to qualify by name, you can build your own qualifiers using the Qualifier annotation. For example, consider the following annotation:

import javax.inject.Qualifier;
import java.lang.annotation.Retention;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Qualifier
@Retention(RUNTIME)
public @interface V8 {
}
import javax.inject.Qualifier
import java.lang.annotation.Retention

import static java.lang.annotation.RetentionPolicy.RUNTIME

@Qualifier
@Retention(RUNTIME)
@interface V8 {
}
import javax.inject.Qualifier
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy.RUNTIME

@Qualifier
@Retention(RUNTIME)
annotation class V8

The above annotation is itself annotated with the @Qualifier annotation to designate it as a qualifier. You can then use the annotation at any injection point in your code. For example:

@Inject Vehicle(@V8 Engine engine) {
        this.engine = engine;
    }
@Inject Vehicle(@V8 Engine engine) {
        this.engine = engine
    }
@Inject constructor(@V8 val engine: Engine) {

Primary and Secondary Beans

Primary is a qualifier that indicates that a bean is the primary bean that should be selected in the case of multiple possible interface implementations.

Consider the following example:

public interface ColorPicker {
    String color();
}
interface ColorPicker {
    String color()
}
interface ColorPicker {
    fun color(): String
}

Given a common interface called ColorPicker that is implemented by multiple classes.

The Primary Bean

import io.micronaut.context.annotation.Primary;
import javax.inject.Singleton;

@Primary
@Singleton
class Green implements ColorPicker {

    @Override
    public String color() {
        return "green";
    }
}

The Primary Bean

import io.micronaut.context.annotation.Primary
import javax.inject.Singleton

@Primary
@Singleton
class Green implements ColorPicker {

    @Override
    String color() {
        return "green"
    }
}

The Primary Bean

import io.micronaut.context.annotation.Primary
import javax.inject.Singleton


@Primary
@Singleton
class Green: ColorPicker {
    override fun color(): String {
        return "green"
    }
}

The Green bean is a ColorPicker , but is annotated with @Primary .

Another Bean of the Same Type

import javax.inject.Singleton;


@Singleton
public class Blue implements ColorPicker {

    @Override
    public String color() {
        return "blue";
    }
}

Another Bean of the Same Type

import javax.inject.Singleton


@Singleton
class Blue implements ColorPicker {

    @Override
    String color() {
        return "blue"
    }
}

Another Bean of the Same Type

import javax.inject.Singleton


@Singleton
class Blue: ColorPicker {
    override fun color(): String {
        return "blue"
    }
}

The Blue bean is also a ColorPicker and hence you have two possible candidates when injecting the ColorPicker interface. Since Green is the primary it will always be favoured.

@Controller("/testPrimary")
public class TestController {

    protected final ColorPicker colorPicker;

    public TestController(ColorPicker colorPicker) { <b>(1)</b>
        this.colorPicker = colorPicker;
    }

    @Get
    public String index() {
        return colorPicker.color();
    }
}
@Controller("/test")
class TestController {

    protected final ColorPicker colorPicker

    TestController(ColorPicker colorPicker) { <b>(1)</b>
        this.colorPicker = colorPicker
    }

    @Get
    String index() {
        colorPicker.color()
    }
}
@Controller("/test")
class TestController(val colorPicker: ColorPicker) { <b>(1)</b>

    @Get
    fun index(): String {
        return colorPicker.color()
    }
}
1 Although there are two ColorPicker beans, Green gets injected due to the @Primary annotation.

If multiple possible candidates are present and no @Primary is defined then a NonUniqueBeanException will be thrown.

In addition to @Primary , there is also aSecondary annotation which causes the opposite effect and allows de-prioritizing a bean.

3.6 Scopes

Micronaut features an extensible bean scoping mechanism based on JSR-330. The following default scopes are supported:

3.6.1 Built-In Scopes

Table 1. Micronaut Built-in Scopes
Type Description

@Singleton

Singleton scope indicates only one instance of the bean should exist

@Context

Context scope indicates that the bean should be created at the same time as the ApplicationContext (eager initialization)

@Prototype

Prototype scope indicates that a new instance of the bean is created each time it is injected

@Infrastructure

Infrastructure scope represents a bean that cannot be overridden or replaced using@Replaces because it is critical to the functioning of the system.

@ThreadLocal

@ThreadLocal scope is a custom scope that associates a bean per thread via a ThreadLocal

@Refreshable

@Refreshable scope is a custom scope that allows a bean’s state to be refreshed via the /refresh endpoint.

@RequestScope

@RequestScope scope is a custom scope that indicates a new instance of the bean is created and associated with each HTTP request

The@Prototype annotation is a synonym for@Bean because the default scope is prototype.

Additional scopes can be added by defining a @Singleton bean that implements theCustomScope interface.

Note that with Micronaut when starting anApplicationContext by default @Singleton scoped beans are created lazily and on demand. This is by design and to optimize startup time.

If this presents a problem for your use case you have the option of using the@Context annotation which binds the lifecycle of your object to the lifecycle of theApplicationContext. In other words when theApplicationContext is started your bean will be created.

Alternatively you can annotate any @Singleton scoped bean with@Parallel which allows parallel initialization of your bean without impacting overall startup time.

If your bean fails to initialize in parallel then the application will be automatically shutdown.

3.6.1.1 Eager Initialization of Singletons

Eager initialization of @Singleton beans maybe desirable in certain scenarios, such as on AWS Lambda where more CPU resources are assigned to Lamdba construction than execution.

You can specify whether you want to initialize eagerly @Singleton scoped beans using the ApplicationContextBuilder interface:

Enabling Eager Initialization of Singletons

public class Application {

    public static void main(String[] args) {
        Micronaut.build(args)
            .eagerInitSingletons(true) <i data-value="1"></i><b>(1)</b>
            .mainClass(Application.class)
            .start();
    }
}
1 Setting eager init to true initializes all singletons

When you use Micronaut in environments such as, you will not have an Application class and instead you extend a Micronaut provided class. In those cases, Micronaut provides methods which you can override to enhance the ApplicationContextBuilder

Override of newApplicationContextBuilder()

public class MyFunctionHandler extends MicronautRequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
...
    @Nonnull
    @Override
    protected ApplicationContextBuilder newApplicationContextBuilder() {
        ApplicationContextBuilder builder = super.newApplicationContextBuilder();
        builder.eagerInitSingletons(true);
        return builder;
    }
    ...
}

@ConfigurationReader beans such as@EachProperty or @ConfigurationProperties are singleton beans. If you want to eager init configuration, but keep other @Singleton scoped beans creation lazy, use eagerInitConfiguration :

Enabling Eager Initialization of Configuration

public class Application {

    public static void main(String[] args) {
        Micronaut.build(args)
            .eagerInitConfiguration(true) <i data-value="1"></i><b>(1)</b>
            .mainClass(Application.class)
            .start();
    }
}
1 Setting eager init to true initializes all configuration reader beans.

3.6.2 Refreshable Scope

TheRefreshable scope is a custom scope that allows a bean’s state to be refreshed via:

  • /refresh endpoint.

  • Publication of aRefreshEvent.

The following example, illustrates the @Refreshable scope behavior.

@Refreshable <b>(1)</b>
public static class WeatherService {
    private String forecast;

    @PostConstruct
    public void init() {
        forecast = "Scattered Clouds " + new SimpleDateFormat("dd/MMM/yy HH:mm:ss.SSS").format(new Date());<b>(2)</b>
    }

    public String latestForecast() {
        return forecast;
    }
}
@Refreshable <b>(1)</b>
static class WeatherService {

    String forecast

    @PostConstruct
    void init() {
        forecast = "Scattered Clouds ${new SimpleDateFormat("dd/MMM/yy HH:mm:ss.SSS").format(new Date())}" <b>(2)</b>
    }

    String latestForecast() {
        return forecast
    }
}
@Refreshable <b>(1)</b>
open class WeatherService {
    private var forecast: String? = null

    @PostConstruct
    fun init() {
        forecast = "Scattered Clouds " + SimpleDateFormat("dd/MMM/yy HH:mm:ss.SSS").format(Date())<b>(2)</b>
    }

    open fun latestForecast(): String? {
        return forecast
    }
}
1 The WeatherService is annotated with @Refreshable scope which stores an instance until a refresh event is triggered
2 The value of the forecast property is set to a fixed value when the bean is created and won’t change until the bean is refreshed

If you invoke the latestForecast() twice, you will see identical responses such as "Scattered Clouds 01/Feb/18 10:29.199" .

When the /refresh endpoint is invoked or aRefreshEvent is published then the instance is invalidated and a new instance is created the next time the object is requested. For example:

applicationContext.publishEvent(new RefreshEvent());
applicationContext.publishEvent(new RefreshEvent())
applicationContext.publishEvent(RefreshEvent())

3.6.3 Scopes on Meta Annotations

Scopes can be defined on Meta annotations that you can then apply to your classes. Consider the following example meta annotation:

import static java.lang.annotation.RetentionPolicy.RUNTIME;

import io.micronaut.context.annotation.Requires;

import javax.inject.Singleton;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;

@Requires(classes = Car.class ) <b>(1)</b>
@Singleton <b>(2)</b>
@Documented
@Retention(RUNTIME)
public @interface Driver {
}
import io.micronaut.context.annotation.Requires

import javax.inject.Singleton
import java.lang.annotation.Documented
import java.lang.annotation.Retention

import static java.lang.annotation.RetentionPolicy.RUNTIME

@Requires(classes = Car.class ) <b>(1)</b>
@Singleton <b>(2)</b>
@Documented
@Retention(RUNTIME)
@interface Driver {
}
import io.micronaut.context.annotation.Requires
import javax.inject.Singleton


@Requires(classes = [Car::class]) <b>(1)</b>
@Singleton <b>(2)</b>
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class Driver
1 The scope declares a requirement on a Car class usingRequires
2 The annotation is declared as @Singleton

In the example above the @Singleton annotation is applied to the @Driver annotation which results in every class that is annotated with @Driver being regarded as singleton.

Note that in this case it is not possible to alter the scope when the annotation is applied. For example, the following will not override the scope declared by @Driver and is invalid:

Declaring Another Scope

@Driver
@Prototype
class Foo {}

If you wish for the scope to be overridable you should instead use theDefaultScope annotation on @Driver which allows a default scope to be specified if none other is present:

Using @DefaultScope

@Requires(classes = Car.class )
@DefaultScope(Singleton.class) <i data-value="1"></i><b>(1)</b>
@Documented
@Retention(RUNTIME)
public @interface Driver {
}
@Requires(classes = Car.class )
@DefaultScope(Singleton.class) <i data-value="1"></i><b>(1)</b>
@Documented
@Retention(RUNTIME)
@interface Driver {
}
@Requires(classes = [Car::class])
@DefaultScope(Singleton::class) <i data-value="1"></i><b>(1)</b>
@Documented
@Retention(RUNTIME)
annotation class Driver
1 DefaultScope is used to declare which scope to be used if none is present

3.7 Bean Factories

In many cases, you may want to make available as a bean a class that is not part of your codebase such as those provided by third-party libraries. In this case, you cannot annotate the already compiled class. Instead, you should implement a@Factory.

A factory is a class annotated with theFactory annotation that provides 1 or more methods annotated with a bean scope annotation. Which annotation you use depends on what scope you want the bean to be in. See the section onfor more information.

The return types of methods annotated with a bean scope annotation are the bean types. This is best illustrated by an example:

@Singleton
class CrankShaft {
}

class V8Engine implements Engine {
    private final int cylinders = 8;
    private final CrankShaft crankShaft;

    public V8Engine(CrankShaft crankShaft) {
        this.crankShaft = crankShaft;
    }

    public String start() {
        return "Starting V8";
    }
}

@Factory
class EngineFactory {

    @Singleton
    Engine v8Engine(CrankShaft crankShaft) {
        return new V8Engine(crankShaft);
    }
}
@Singleton
class CrankShaft {
}

class V8Engine implements Engine {
    final int cylinders = 8
    final CrankShaft crankShaft

    V8Engine(CrankShaft crankShaft) {
        this.crankShaft = crankShaft
    }

    String start() {
        "Starting V8"
    }
}

@Factory
class EngineFactory {

    @Singleton
    Engine v8Engine(CrankShaft crankShaft) {
        new V8Engine(crankShaft)
    }
}
@Singleton
internal class CrankShaft

internal class V8Engine(private val crankShaft: CrankShaft) : Engine {
    private val cylinders = 8

    override fun start(): String {
        return "Starting V8"
    }
}

@Factory
internal class EngineFactory {

    @Singleton
    fun v8Engine(crankShaft: CrankShaft): Engine {
        return V8Engine(crankShaft)
    }
}

In this case, the V8Engine is built by the EngineFactory class' v8Engine method. Note that you can inject parameters into the method and these parameters will be resolved as beans. The resulting V8Engine bean will be a singleton.

A factory can have multiple methods annotated with bean scope annotations, each one returning a distinct bean type.

If you take this approach, then you should not invoke other bean methods internally within the class. Instead, inject the types via parameters.
To allow the resulting bean to participate in the application context shutdown process, annotate the method with@Bean and set the preDestroy argument to the name of the method that should be called to close the bean.

Programmatically Disabling Beans

Factory methods can return throw DisabledBeanException to allow for beans to be disabled conditionally. The use of@Requires should always be the preferred method to conditionally create beans and throwing an exception a factory method should only be done if using@Requires is not possible.

For example:

public interface Engine {
    Integer getCylinders();
}

@EachProperty("engines")
public class EngineConfiguration implements Toggleable {

    private boolean enabled = true;
    private Integer cylinders;

    @NotNull
    public Integer getCylinders() {
        return cylinders;
    }

    public void setCylinders(Integer cylinders) {
        this.cylinders = cylinders;
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

}

@Factory
public class EngineFactory {

    @EachBean(EngineConfiguration.class)
    public Engine buildEngine(EngineConfiguration engineConfiguration) {
        if (engineConfiguration.isEnabled()) {
            return engineConfiguration::getCylinders;
        } else {
            throw new DisabledBeanException("Engine configuration disabled");
        }
    }
}
interface Engine {
    Integer getCylinders()
}

@EachProperty("engines")
class EngineConfiguration implements Toggleable {
    boolean enabled = true
    @NotNull
    Integer cylinders
}

@Factory
class EngineFactory {

    @EachBean(EngineConfiguration)
    Engine buildEngine(EngineConfiguration engineConfiguration) {
        if (engineConfiguration.enabled) {
            (Engine){ -> engineConfiguration.cylinders }
        } else {
            throw new DisabledBeanException("Engine configuration disabled")
        }
    }
}
interface Engine {
    fun getCylinders(): Int
}

@EachProperty("engines")
class EngineConfiguration : Toggleable {

    val enabled = true

    @NotNull
    val cylinders: Int? = null

    override fun isEnabled(): Boolean {
        return enabled
    }
}

@Factory
class EngineFactory {

    @EachBean(EngineConfiguration::class)
    fun buildEngine(engineConfiguration: EngineConfiguration): Engine? {
        return if (engineConfiguration.isEnabled) {
            object : Engine {
                override fun getCylinders(): Int {
                    return engineConfiguration.cylinders!!
                }
            }
        } else {
            throw DisabledBeanException("Engine configuration disabled")
        }
    }
}

Injection Point

A common use case with factories is to take advantage of annotation metadata from the point at which an object is injected such that behaviour can be modified based on said metadata.

Consider an annotation such as the following:

@Documented
@Retention(RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Cylinders {
    int value() default 8;
}
@Documented
@Retention(RUNTIME)
@Target(ElementType.PARAMETER)
@interface Cylinders {
    int value() default 8
}
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.VALUE_PARAMETER)
annotation class Cylinders(val value: Int = 8)

The above annotation could be used to customize the type of engine we want to inject into a vehicle at the point at which the injection point is defined:

@Singleton
class Vehicle {
    private final Engine engine;
    Vehicle(@Cylinders(6) Engine engine) {
        this.engine = engine;
    }

    String start() {
        return engine.start();
    }
}
@Singleton
class Vehicle {
    private final Engine engine
    Vehicle(@Cylinders(6) Engine engine) {
        this.engine = engine
    }

    String start() {
        return engine.start()
    }
}
@Singleton
internal class Vehicle(@param:Cylinders(6) private val engine: Engine) {
    fun start(): String {
        return engine.start()
    }
}

The above Vehicle class specifies an annotation value of @Cylinders(6) indicating an Engine of six cylinders is needed.

To implement this use case you can define a factory that accepts theInjectionPoint instance which can be used to analyze the annotation values defined:

@Factory
class EngineFactory {

    @Prototype
    Engine v8Engine(InjectionPoint<?> injectionPoint, CrankShaft crankShaft) { <b>(1)</b>
        final int cylinders = injectionPoint
                .getAnnotationMetadata()
                .intValue(Cylinders.class).orElse(8); <b>(2)</b>
        switch (cylinders) { <b>(3)</b>
            case 6:
                return new V6Engine(crankShaft);
            case 8:
                return new V8Engine(crankShaft);
            default:
                throw new IllegalArgumentException("Unsupported number of cylinders specified: " + cylinders);
        }
    }
}
@Factory
class EngineFactory {

    @Prototype
    Engine v8Engine(InjectionPoint<?> injectionPoint, CrankShaft crankShaft) { <b>(1)</b>
        final int cylinders = injectionPoint
                .getAnnotationMetadata()
                .intValue(Cylinders.class).orElse(8) <b>(2)</b>
        switch (cylinders) { <b>(3)</b>
            case 6:
                return new V6Engine(crankShaft)
            case 8:
                return new V8Engine(crankShaft)
            default:
                throw new IllegalArgumentException("Unsupported number of cylinders specified: $cylinders")
        }
    }
}
@Factory
internal class EngineFactory {

    @Prototype
    fun v8Engine(injectionPoint: InjectionPoint<*>, crankShaft: CrankShaft): Engine { <b>(1)</b>
        val cylinders = injectionPoint
                .annotationMetadata
                .intValue(Cylinders::class.java).orElse(8) <b>(2)</b>
        return when (cylinders) { <b>(3)</b>
            6 -> V6Engine(crankShaft)
            8 -> V8Engine(crankShaft)
            else -> throw IllegalArgumentException("Unsupported number of cylinders specified: $cylinders")
        }
    }
}
1 The factory method defines a parameter of typeInjectionPoint.
2 The annotation metadata is used to obtain the value of the @Cylinder annotation
3 The value is used to construct an engine, throwing an exception if an engine cannot be constructed.
It is important to note that the factory is declared as@Prototype scope so that the method is invoked for each injection point. If the V8Engine and V6Engine types are required to be singletons then the factory should use a map to ensure the objects are only constructed once.

3.8 Conditional Beans

At times you may want a bean to load conditionally based on various potential factors including the classpath, the configuration, the presence of other beans etc.

TheRequires annotation provides the ability to define one or many conditions on a bean.

Consider the following example:

Using @Requires

@Singleton
@Requires(beans = DataSource.class)
@Requires(property = "datasource.url")
public class JdbcBookService implements BookService {

    DataSource dataSource;

    public JdbcBookService(DataSource dataSource) {
        this.dataSource = dataSource;
    }

Using @Requires

@Singleton
@Requires(beans = DataSource.class)
@Requires(property = "datasource.url")
class JdbcBookService implements BookService {

    DataSource dataSource

Using @Requires

@Singleton
@Requirements(Requires(beans = [DataSource::class]), Requires(property = "datasource.url"))
class JdbcBookService(internal var dataSource: DataSource) : BookService {

The above bean defines two requirements. The first indicates that a DataSource bean must be present for the bean to load. The second requirement ensures that the datasource.url property is set before loading the JdbcBookService bean.

Kotlin currently does not support repeatable annotations. Use the @Requirements annotation when multiple requires are needed. For example, @Requirements(Requires(…​), Requires(…​)) . See https://youtrack.jetbrains.com/issue/KT-12794 to track this feature.

If you have multiple requirements that you find you may need to repeat on multiple beans then you can define a meta-annotation with the requirements:

Using a @Requires meta-annotation

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PACKAGE, ElementType.TYPE})
@Requires(beans = DataSource.class)
@Requires(property = "datasource.url")
public @interface RequiresJdbc {
}

Using a @Requires meta-annotation

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target([ElementType.PACKAGE, ElementType.TYPE])
@Requires(beans = DataSource.class)
@Requires(property = "datasource.url")
@interface RequiresJdbc {
}

Using a @Requires meta-annotation

@Documented
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FILE)
@Requirements(Requires(beans = [DataSource::class]), Requires(property = "datasource.url"))
annotation class RequiresJdbc

In the above example an annotation called RequiresJdbc is defined that can then be used on the JdbcBookService instead:

Using a meta-annotation

@RequiresJdbc
public class JdbcBookService implements BookService {
    ...
}

If you have multiple beans that need to fulfill a given requirement before loading then you may want to consider a bean configuration group, as explained in the next section.

Configuration Requirements

The@Requires annotation is very flexible and can be used for a variety of use cases. The following table summarizes some of the possibilities:

Table 1. Using @Requires
Requirement Example

Require the presence of one ore more classes

@Requires(classes=javax.servlet.Servlet)

Require the absence of one ore more classes

@Requires(missing=javax.servlet.Servlet)

Require the presence one or more beans

@Requires(beans=javax.sql.DataSource)

Require the absence of one or more beans

@Requires(missingBeans=javax.sql.DataSource)

Require the environment to be applied

@Requires(env="test")

Require the environment to not be applied

@Requires(notEnv="test")

Require the presence of another configuration package

@Requires(configuration="foo.bar")

Require the absence of another configuration package

@Requires(missingConfigurations="foo.bar")

Require particular SDK version

@Requires(sdk=Sdk.JAVA, value="1.8")

Requires classes annotated with the given annotations to be available to the application via package scanning

@Requires(entities=javax.persistence.Entity)

Require a property with an optional value

@Requires(property="data-source.url")

Require a property to not be part of the configuration

@Requires(missingProperty="data-source.url")

Require the presence of one or more files in the file system

@Requires(resources="file:/path/to/file")

Require the presence of one or more classpath resources

@Requires(resources="classpath:myFile.properties")

Require the current operating system to be in the list

@Requires(os={Requires.Family.WINDOWS})

Require the current operating system to not be in the list

@Requires(notOs={Requires.Family.WINDOWS})

Additional Notes on Property Requirements.

Adding a requirement on a property has some additional functionality. You can require the property to be a certain value, not be a certain value, and use a default in those checks if its not set.

@Requires(property="foo") <i data-value="1"></i><b>(1)</b>
@Requires(property="foo", value="John") <i data-value="2"></i><b>(2)</b>
@Requires(property="foo", value="John", defaultValue="John") <i data-value="3"></i><b>(3)</b>
@Requires(property="foo", notEquals="Sally") <i data-value="4"></i><b>(4)</b>
1 Requires the property to be set
2 Requires the property to be "John"
3 Requires the property to be "John" or not set
4 Requires the property to not be "Sally" or not set

Debugging Conditional Beans

If you have multiple conditions and complex requirements it may become difficult to understand why a particular bean has not been loaded.

To help resolve issues with conditional beans you can enable debug logging for the io.micronaut.context.condition package which will log the reasons why beans were not loaded.

logback.xml

<logger name="io.micronaut.context.condition" level="DEBUG"/>

3.9 Bean Replacement

One significant difference between Micronaut’s Dependency Injection system and Spring is the way beans can be replaced.

In a Spring application, beans have names and can effectively be overridden simply by creating a bean with the same name, regardless of the type of the bean. Spring also has the notion of bean registration order, hence in Spring Boot you have @AutoConfigureBefore and @AutoConfigureAfter that control how beans override each other.

This strategy leads to difficult to debug problems, for example:

  • Bean loading order changes, leading to unexpected results

  • A bean with the same name overrides another bean with a different type

To avoid these problems, Micronaut’s DI has no concept of bean names or load order. Beans have a type and aQualifier. You cannot override a bean of a completely different type with another.

A useful benefit of Spring’s approach is that it allows overriding existing beans to customize behaviour. In order to support the same ability, Micronaut’s DI provides an explicit@Replaces annotation, which integrates nicely with support forand clearly documents and expresses the intention of the developer.

Any existing bean can be replaced by another bean that declares@Replaces. For example, consider the following class:

JdbcBookService

@Singleton
@Requires(beans = DataSource.class)
@Requires(property = "datasource.url")
public class JdbcBookService implements BookService {

    DataSource dataSource;

    public JdbcBookService(DataSource dataSource) {
        this.dataSource = dataSource;
    }

JdbcBookService

@Singleton
@Requires(beans = DataSource.class)
@Requires(property = "datasource.url")
class JdbcBookService implements BookService {

    DataSource dataSource

JdbcBookService

@Singleton
@Requirements(Requires(beans = [DataSource::class]), Requires(property = "datasource.url"))
class JdbcBookService(internal var dataSource: DataSource) : BookService {

You can define a class in src/test/java that replaces this class just for your tests:

Using @Replaces

@Replaces(JdbcBookService.class) <b>(1)</b>
@Singleton
public class MockBookService implements BookService {

    Map<String, Book> bookMap = new LinkedHashMap<>();

    @Override
    public Book findBook(String title) {
        return bookMap.get(title);
    }
}

Using @Replaces

@Replaces(JdbcBookService.class) <b>(1)</b>
@Singleton
class MockBookService implements BookService {

    Map<String, Book> bookMap = [:]

    @Override
    Book findBook(String title) {
        bookMap.get(title)
    }
}

Using @Replaces

@Replaces(JdbcBookService::class) <b>(1)</b>
@Singleton
class MockBookService : BookService {

    var bookMap: Map<String, Book> = LinkedHashMap()

    override fun findBook(title: String): Book? {
        return bookMap[title]
    }
}
1 The MockBookService declares that it replaces JdbcBookService

Factory Replacement

The @Replaces annotation also supports a factory argument. That argument allows the replacement of factory beans in their entirety or specific types created by the factory.

For example, it may be desired to replace all or part of the given factory class:

BookFactory

@Factory
public class BookFactory {

    @Singleton
    Book novel() {
        return new Book("A Great Novel");
    }

    @Singleton
    TextBook textBook() {
        return new TextBook("Learning 101");
    }
}

BookFactory

@Factory
class BookFactory {

    @Singleton
    Book novel() {
        new Book('A Great Novel')
    }

    @Singleton
    TextBook textBook() {
        new TextBook('Learning 101')
    }
}

BookFactory

@Factory
class BookFactory {

    @Singleton
    internal fun novel(): Book {
        return Book("A Great Novel")
    }

    @Singleton
    internal fun textBook(): TextBook {
        return TextBook("Learning 101")
    }
}
To replace a factory in its entirety, it is necessary that your factory methods match the return types of all of the methods in the replaced factory.

In this example, the BookFactory#textBook() will not be replaced because this factory does not have a factory method that returns a TextBook.

CustomBookFactory

@Factory
@Replaces(factory = BookFactory.class)
public class CustomBookFactory {

    @Singleton
    Book otherNovel() {
        return new Book("An OK Novel");
    }
}

CustomBookFactory

@Factory
@Replaces(factory = BookFactory.class)
class CustomBookFactory {

    @Singleton
    Book otherNovel() {
        new Book('An OK Novel')
    }
}

CustomBookFactory

@Factory
@Replaces(factory = BookFactory::class)
class CustomBookFactory {

    @Singleton
    internal fun otherNovel(): Book {
        return Book("An OK Novel")
    }
}

It may be the case that you don’t wish for the factory methods to be replaced, except for a select few. For that use case, you can apply the @Replaces annotation on the method and denote the factory that it should apply to.

TextBookFactory

@Factory
public class TextBookFactory {

    @Singleton
    @Replaces(value = TextBook.class, factory = BookFactory.class)
    TextBook textBook() {
        return new TextBook("Learning 305");
    }
}

TextBookFactory

@Factory
class TextBookFactory {

    @Singleton
    @Replaces(value = TextBook.class, factory = BookFactory.class)
    TextBook textBook() {
        new TextBook('Learning 305')
    }
}

TextBookFactory

@Factory
class TextBookFactory {

    @Singleton
    @Replaces(value = TextBook::class, factory = BookFactory::class)
    internal fun textBook(): TextBook {
        return TextBook("Learning 305")
    }
}

The BookFactory#novel() method will not be replaced because the TextBook class is defined in the annotation.

Default Implementation

When exposing an API, it may be desirable for the default implementation of an interface to not be exposed as part of the public API. Doing so would prevent users from being able to replace the implementation because they would not be able to reference the class. The solution is to annotate the interface with DefaultImplementation to indicate which implementation should be replaced if a user creates a bean that @Replaces(YourInterface.class) .

For example consider:

A public API contract

import io.micronaut.context.annotation.DefaultImplementation;

@DefaultImplementation(DefaultResponseStrategy.class)
public interface ResponseStrategy {

}
import io.micronaut.context.annotation.DefaultImplementation

@DefaultImplementation(DefaultResponseStrategy)
interface ResponseStrategy {
}
import io.micronaut.context.annotation.DefaultImplementation

@DefaultImplementation(DefaultResponseStrategy::class)
interface ResponseStrategy

The default implementation

import javax.inject.Singleton;

@Singleton
class DefaultResponseStrategy implements ResponseStrategy {

}
import javax.inject.Singleton

@Singleton
class DefaultResponseStrategy implements ResponseStrategy {

}
import javax.inject.Singleton

@Singleton
internal class DefaultResponseStrategy : ResponseStrategy

The custom implementation

import io.micronaut.context.annotation.Replaces;
import javax.inject.Singleton;

@Singleton
@Replaces(ResponseStrategy.class)
public class CustomResponseStrategy implements ResponseStrategy {

}
import io.micronaut.context.annotation.Replaces
import javax.inject.Singleton

@Singleton
@Replaces(ResponseStrategy)
class CustomResponseStrategy implements ResponseStrategy {

}
import io.micronaut.context.annotation.Replaces
import javax.inject.Singleton

@Singleton
@Replaces(ResponseStrategy::class)
class CustomResponseStrategy : ResponseStrategy

In the above example, the CustomResponseStrategy will replace the DefaultResponseStrategy because the DefaultImplementation annotation points to the DefaultResponseStrategy .

3.10 Bean Configurations

A bean@Configuration is a grouping of multiple bean definitions within a package.

The @Configuration annotation is applied at the package level and informs Micronaut that the beans defined with the package form a logical grouping.

The @Configuration annotation is typically applied to package-info class. For example:

package-info.groovy

@Configuration
package my.package

import io.micronaut.context.annotation.Configuration

Where this grouping becomes useful is when the bean configuration is made conditional via the @Requires annotation. For example:

package-info.groovy

@Configuration
@Requires(beans = javax.sql.DataSource)
package my.package

In the above example, all bean definitions within the annotated package will only be loaded and made available if a javax.sql.DataSource bean is present. This allows you to implement conditional auto-configuration of bean definitions.

INFO: Java and Kotlin also support this functionality via package-info.java . Kotlin does not support a package-info.kt as of version 1.3.

3.11 Life-Cycle Methods

When The Context Starts

If you wish for a particular method to be invoked when a bean is constructed then you can use the javax.annotation.PostConstruct annotation:

import javax.annotation.PostConstruct; <b>(1)</b>
import javax.inject.Singleton;

@Singleton
public class V8Engine implements Engine {
    private int cylinders = 8;
    private boolean initialized = false; <b>(2)</b>

    public String start() {
        if(!initialized) {
            throw new IllegalStateException("Engine not initialized!");
        }

        return "Starting V8";
    }

    @Override
    public int getCylinders() {
        return cylinders;
    }

    public boolean isIntialized() {
        return this.initialized;
    }

    @PostConstruct <b>(3)</b>
    public void initialize() {
        this.initialized = true;
    }
}
import javax.annotation.PostConstruct <b>(1)</b>
import javax.inject.Singleton

@Singleton
class V8Engine implements Engine {
    int cylinders = 8
    boolean initialized = false <b>(2)</b>

    String start() {
        if(!initialized) throw new IllegalStateException("Engine not initialized!")

        return "Starting V8"
    }

    @PostConstruct <b>(3)</b>
    void initialize() {
        this.initialized = true
    }
}
import javax.annotation.PostConstruct
import javax.inject.Singleton


@Singleton
class V8Engine : Engine {
    override val cylinders = 8
    var isIntialized = false
        private set <b>(2)</b>

    override fun start(): String {
        check(isIntialized) { "Engine not initialized!" }

        return "Starting V8"
    }

    @PostConstruct <b>(3)</b>
    fun initialize() {
        this.isIntialized = true
    }
}
1 The PostConstruct annotation is imported
2 A field is defined that requires initialization
3 A method is annotated with @PostConstruct and will be invoked once the object is constructed and fully injected.

When The Context Closes

If you wish for a particular method to be invoked when the context is closed then you can use the javax.annotation.PreDestroy annotation:

import javax.annotation.PreDestroy; <b>(1)</b>
import javax.inject.Singleton;
import java.util.concurrent.atomic.AtomicBoolean;

@Singleton
public class PreDestroyBean implements AutoCloseable {

    AtomicBoolean stopped = new AtomicBoolean(false);

    @PreDestroy <b>(2)</b>
    @Override
    public void close() throws Exception {
        stopped.compareAndSet(false, true);
    }
}
import javax.annotation.PreDestroy <b>(1)</b>
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Singleton

@Singleton
class PreDestroyBean implements AutoCloseable {

    AtomicBoolean stopped = new AtomicBoolean(false)

    @PreDestroy <b>(2)</b>
    @Override
    void close() throws Exception {
        stopped.compareAndSet(false, true)
    }
}
import javax.annotation.PreDestroy
import javax.inject.Singleton
import java.util.concurrent.atomic.AtomicBoolean

@Singleton
class PreDestroyBean : AutoCloseable {

    internal var stopped = AtomicBoolean(false)

    @PreDestroy <b>(2)</b>
    @Throws(Exception::class)
    override fun close() {
        stopped.compareAndSet(false, true)
    }
}
1 The PreDestroy annotation is imported
2 A method is annotated with @PreDestroy and will be invoked when the context is closed.

For factory beans, the preDestroy value in theBean annotation can be used to tell Micronaut which method to invoke.

import io.micronaut.context.annotation.Bean;
import io.micronaut.context.annotation.Factory;

import javax.inject.Singleton;

@Factory
public class ConnectionFactory {

    @Bean(preDestroy = "stop") <b>(1)</b>
    @Singleton
    public Connection connection() {
        return new Connection();
    }
}
import io.micronaut.context.annotation.Bean
import io.micronaut.context.annotation.Factory

import javax.inject.Singleton

@Factory
class ConnectionFactory {

    @Bean(preDestroy = "stop") <b>(1)</b>
    @Singleton
    Connection connection() {
        new Connection()
    }
}
import io.micronaut.context.annotation.Bean
import io.micronaut.context.annotation.Factory

import javax.inject.Singleton

@Factory
class ConnectionFactory {

    @Bean(preDestroy = "stop") <b>(1)</b>
    @Singleton
    fun connection(): Connection {
        return Connection()
    }
}
import java.util.concurrent.atomic.AtomicBoolean;

public class Connection {

    AtomicBoolean stopped = new AtomicBoolean(false);

    public void stop() { <b>(2)</b>
        stopped.compareAndSet(false, true);
    }

}
import java.util.concurrent.atomic.AtomicBoolean

class Connection {

    AtomicBoolean stopped = new AtomicBoolean(false)

    void stop() { <b>(2)</b>
        stopped.compareAndSet(false, true)
    }

}
import java.util.concurrent.atomic.AtomicBoolean

class Connection {

    internal var stopped = AtomicBoolean(false)

    fun stop() { <b>(2)</b>
        stopped.compareAndSet(false, true)
    }

}
1 The preDestroy value is set on the annotation
2 The annotation value matches the method name
Just simply implementing the Closeable or AutoCloseable interfaces is not enough to get a bean to close with the context. One of the above methods must be used.

3.12 Context Events

Micronaut supports a general event system through the context. The ApplicationEventPublisher API is used to publish events and the ApplicationEventListener API is used to listen to events. The event system is not limited to the events that Micronaut publishes and can be used for custom events created by the users.

Publishing Events

The ApplicationEventPublisher API supports events of any type, however all events that Micronaut publishes extendApplicationEvent.

To publish an event, obtain an instance of ApplicationEventPublisher either directly from the context or through dependency injection, and execute the publishEvent method with your event object.

Publishing an Event

public class SampleEvent {
    private String message = "Something happened";

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

import io.micronaut.context.event.ApplicationEventPublisher;
import javax.inject.Inject;
import javax.inject.Singleton;

@Singleton
public class SampleEventEmitterBean {

    @Inject
    ApplicationEventPublisher eventPublisher;

    public void publishSampleEvent() {
        eventPublisher.publishEvent(new SampleEvent());
    }

}

Publishing an Event

class SampleEvent {
    String message = "Something happened"
}

import io.micronaut.context.event.ApplicationEventPublisher
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class SampleEventEmitterBean {

    @Inject
    ApplicationEventPublisher eventPublisher

    void publishSampleEvent() {
        eventPublisher.publishEvent(new SampleEvent())
    }

}

Publishing an Event

data class SampleEvent(val message: String = "Something happened")

import io.micronaut.context.event.ApplicationEventPublisher
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class SampleEventEmitterBean {

    @Inject
    internal var eventPublisher: ApplicationEventPublisher? = null

    fun publishSampleEvent() {
        eventPublisher!!.publishEvent(SampleEvent())
    }

}
Publishing an event is synchronous by default! The publishEvent method will not return until all listeners have been executed. Move this work off to a thread pool if it is time intensive.

Listening for Events

To listen to an event, register a bean that implements ApplicationEventListener where the generic type is the type of event the listener should be executed for.

Listening for Events with ApplicationEventListener
import io.micronaut.context.event.ApplicationEventListener;
import io.micronaut.docs.context.events.SampleEvent;

@Singleton
public class SampleEventListener implements ApplicationEventListener<SampleEvent> {
    private int invocationCounter = 0;

    @Override
    public void onApplicationEvent(SampleEvent event) {
        invocationCounter++;
    }

    public int getInvocationCounter() {
        return invocationCounter;
    }
}

import io.micronaut.context.ApplicationContext;
import io.micronaut.docs.context.events.SampleEventEmitterBean;
import org.junit.Test;

import static org.junit.Assert.assertEquals;

public class SampleEventListenerSpec {

    @Test
    public void testEventListenerIsNotified() {
        try (ApplicationContext context = ApplicationContext.run()) {
            SampleEventEmitterBean emitter = context.getBean(SampleEventEmitterBean.class);
            SampleEventListener listener = context.getBean(SampleEventListener.class);
            assertEquals(0, listener.getInvocationCounter());
            emitter.publishSampleEvent();
            assertEquals(1, listener.getInvocationCounter());
        }

    }

}
Listening for Events with ApplicationEventListener
import io.micronaut.context.event.ApplicationEventListener
import io.micronaut.docs.context.events.SampleEvent

@Singleton
class SampleEventListener implements ApplicationEventListener<SampleEvent> {
    int invocationCounter = 0

    @Override
    void onApplicationEvent(SampleEvent event) {
        invocationCounter++
    }
}

import io.micronaut.context.ApplicationContext
import io.micronaut.docs.context.events.SampleEventEmitterBean
import spock.lang.Specification

class SampleEventListenerSpec extends Specification {

    void "test event listener is notified"() {
        given:
        ApplicationContext context = ApplicationContext.run()
        SampleEventEmitterBean emitter = context.getBean(SampleEventEmitterBean)
        SampleEventListener listener = context.getBean(SampleEventListener)
        assert listener.invocationCounter == 0

        when:
        emitter.publishSampleEvent()

        then:
        listener.invocationCounter == 1

        cleanup:
        context.close()
    }
}
Listening for Events with ApplicationEventListener
import io.micronaut.context.event.ApplicationEventListener
import io.micronaut.docs.context.events.SampleEvent

@Singleton
class SampleEventListener : ApplicationEventListener<SampleEvent> {
    var invocationCounter = 0

    override fun onApplicationEvent(event: SampleEvent) {
        invocationCounter++
    }
}

import io.kotlintest.shouldBe
import io.kotlintest.specs.AnnotationSpec
import io.micronaut.context.ApplicationContext
import io.micronaut.docs.context.events.SampleEventEmitterBean

class SampleEventListenerSpec : AnnotationSpec() {

    @Test
    fun testEventListenerWasNotified() {
        val context = ApplicationContext.run()
        val emitter = context.getBean(SampleEventEmitterBean::class.java)
        val listener = context.getBean(SampleEventListener::class.java)
        listener.invocationCounter.shouldBe(0)
        emitter.publishSampleEvent()
        listener.invocationCounter.shouldBe(1)

        context.close()
    }
}
Thesupports method can be overridden to further clarify events that should be processed.

Alternatively you can use the@EventListener annotation if you do not wish to specifically implement an interface or utilize one of the built in events likeStartupEvent andShutdownEvent:

Listening for Events with @EventListener
import io.micronaut.docs.context.events.SampleEvent;
import io.micronaut.context.event.StartupEvent;
import io.micronaut.context.event.ShutdownEvent;
import io.micronaut.runtime.event.annotation.EventListener;

@Singleton
public class SampleEventListener {
    private int invocationCounter = 0;

    @EventListener
    public void onSampleEvent(SampleEvent event) {
        invocationCounter++;
    }

    @EventListener
    public void onStartupEvent(StartupEvent event) {
        // startup logic here
    }

    @EventListener
    public void onShutdownEvent(ShutdownEvent event) {
        // shutdown logic here
    }

    public int getInvocationCounter() {
        return invocationCounter;
    }
}
Listening for Events with @EventListener
import io.micronaut.docs.context.events.SampleEvent
import io.micronaut.context.event.StartupEvent
import io.micronaut.context.event.ShutdownEvent
import io.micronaut.runtime.event.annotation.EventListener

@Singleton
class SampleEventListener {
    int invocationCounter = 0

    @EventListener
    void onSampleEvent(SampleEvent event) {
        invocationCounter++
    }

    @EventListener
    void onStartupEvent(StartupEvent event) {
        // startup logic here
    }

    @EventListener
    void onShutdownEvent(ShutdownEvent event) {
        // shutdown logic here
    }
}
Listening for Events with @EventListener
import io.micronaut.docs.context.events.SampleEvent
import io.micronaut.context.event.StartupEvent
import io.micronaut.context.event.ShutdownEvent
import io.micronaut.runtime.event.annotation.EventListener

@Singleton
class SampleEventListener {
    var invocationCounter = 0

    @EventListener
    internal fun onSampleEvent(event: SampleEvent) {
        invocationCounter++
    }

    @EventListener
    internal fun onStartupEvent(event: StartupEvent) {
        // startup logic here
    }

    @EventListener
    internal fun onShutdownEvent(event: ShutdownEvent) {
        // shutdown logic here
    }
}

If your listener performs work that could take a while then you can use the@Async annotation to run the operation on a separate thread:

Asynchronously listening for Events with @EventListener
import io.micronaut.docs.context.events.SampleEvent;
import io.micronaut.runtime.event.annotation.EventListener;
import io.micronaut.scheduling.annotation.Async;

@Singleton
public class SampleEventListener {
    private AtomicInteger invocationCounter = new AtomicInteger(0);

    @EventListener
    @Async
    public void onSampleEvent(SampleEvent event) {
        invocationCounter.getAndIncrement();
    }

    public int getInvocationCounter() {
        return invocationCounter.get();
    }
}

import io.micronaut.context.ApplicationContext;
import io.micronaut.docs.context.events.SampleEventEmitterBean;
import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.Matchers.equalTo;

public class SampleEventListenerSpec {

    @Test
    public void testEventListenerIsNotified() {
        try (ApplicationContext context = ApplicationContext.run()) {
            SampleEventEmitterBean emitter = context.getBean(SampleEventEmitterBean.class);
            SampleEventListener listener = context.getBean(SampleEventListener.class);
            assertEquals(0, listener.getInvocationCounter());
            emitter.publishSampleEvent();
            await().atMost(10, SECONDS).until(listener::getInvocationCounter, equalTo(1));
        }
    }

}
Asynchronously listening for Events with @EventListener
import io.micronaut.docs.context.events.SampleEvent
import io.micronaut.runtime.event.annotation.EventListener
import io.micronaut.scheduling.annotation.Async

@Singleton
class SampleEventListener {
    AtomicInteger invocationCounter = new AtomicInteger(0)

    @EventListener
    @Async
    void onSampleEvent(SampleEvent event) {
        invocationCounter.getAndIncrement()
    }
}

import io.micronaut.context.ApplicationContext
import io.micronaut.docs.context.events.SampleEventEmitterBean
import spock.lang.Specification
import spock.util.concurrent.PollingConditions

class SampleEventListenerSpec extends Specification {

    void "test event listener is notified"() {
        given:
        ApplicationContext context = ApplicationContext.run()
        SampleEventEmitterBean emitter = context.getBean(SampleEventEmitterBean)
        SampleEventListener listener = context.getBean(SampleEventListener)
        assert listener.invocationCounter.get() == 0

        when:
        emitter.publishSampleEvent()

        then:
        new PollingConditions().eventually {
            listener.invocationCounter.get() == 1
        }

        cleanup:
        context.close()

    }
}
Asynchronously listening for Events with @EventListener
import io.micronaut.docs.context.events.SampleEvent
import io.micronaut.runtime.event.annotation.EventListener
import io.micronaut.scheduling.annotation.Async
import java.util.concurrent.atomic.AtomicInteger

@Singleton
open class SampleEventListener {

    var invocationCounter = AtomicInteger(0)

    @EventListener
    @Async
    open fun onSampleEvent(event: SampleEvent) {
        println("Incrementing invocation counter...")
        invocationCounter.getAndIncrement()
    }
}

import io.kotlintest.eventually
import io.kotlintest.seconds
import io.kotlintest.shouldBe
import io.kotlintest.specs.AnnotationSpec
import io.micronaut.context.ApplicationContext
import io.micronaut.docs.context.events.SampleEventEmitterBean
import org.opentest4j.AssertionFailedError

class SampleEventListenerSpec : AnnotationSpec() {

    @Test
//    @Ignore // TODO can't get this to pass on CI, any help is welcome
    fun testEventListenerWasNotified() {
        val context = ApplicationContext.run()
        val emitter = context.getBean(SampleEventEmitterBean::class.java)
        val listener = context.getBean(SampleEventListener::class.java)
        listener.invocationCounter.get().shouldBe(0)
        emitter.publishSampleEvent()

        eventually(5.seconds,  AssertionFailedError::class.java) {
            println("Current value of counter: " + listener.invocationCounter.get())
            listener.invocationCounter.get().shouldBe(1)
        }

        context.close()
    }
}

The event listener will by default run on the scheduled executor. You can configure this thread pool as required in application.yml :

Configuring Scheduled Task Thread Pool

micronaut:
    executors:
        scheduled:
            type: scheduled
            core-pool-size: 30

3.13 Bean Events

You can hook into the creation of beans using one of the following interfaces:

  • BeanInitializedEventListener - allows modifying or replacing of a bean after the properties have been set but prior to @PostConstruct event hooks.

  • BeanCreatedEventListener - allows modifying or replacing of a bean after the bean is fully initialized and all @PostConstruct hooks called.

The BeanInitializedEventListener interface is commonly used in combination withFactory beans. Consider the following example:

public class V8Engine implements Engine {
    private final int cylinders = 8;
    private double rodLength; <b>(1)</b>

    public V8Engine(double rodLength) {
        this.rodLength = rodLength;
    }

    public String start() {
        return "Starting V" + String.valueOf(getCylinders()) + " [rodLength=" + String.valueOf(getRodLength()) + "]";
    }

    public final int getCylinders() {
        return cylinders;
    }

    public double getRodLength() {
        return rodLength;
    }

    public void setRodLength(double rodLength) {
        this.rodLength = rodLength;
    }
}

@Factory
public class EngineFactory {

    private V8Engine engine;
    private double rodLength = 5.7;

    @PostConstruct
    public void initialize() {
        engine = new V8Engine(rodLength); <b>(2)</b>
    }

    @Singleton
    public Engine v8Engine() {
        return engine;<b>(3)</b>
    }

    public void setRodLength(double rodLength) {
        this.rodLength = rodLength;
    }
}

@Singleton
public class EngineInitializer implements BeanInitializedEventListener<EngineFactory> { <b>(4)</b>
    @Override
    public EngineFactory onInitialized(BeanInitializingEvent<EngineFactory> event) {
        EngineFactory engineFactory = event.getBean();
        engineFactory.setRodLength(6.6);<b>(5)</b>
        return engineFactory;
    }
}
class V8Engine implements Engine {
    final int cylinders = 8
    double rodLength <b>(1)</b>

    String start() {
        return "Starting V${cylinders} [rodLength=$rodLength]"
    }
}

@Factory
class EngineFactory {
    private V8Engine engine
    double rodLength = 5.7

    @PostConstruct
    void initialize() {
        engine = new V8Engine(rodLength: rodLength) <b>(2)</b>
    }

    @Singleton
    Engine v8Engine() {
        return engine <b>(3)</b>
    }
}

@Singleton
class EngineInitializer implements BeanInitializedEventListener<EngineFactory> { <b>(4)</b>
    @Override
    EngineFactory onInitialized(BeanInitializingEvent<EngineFactory> event) {
        EngineFactory engineFactory = event.bean
        engineFactory.rodLength = 6.6 <b>(5)</b>
        return event.bean
    }
}
class V8Engine(var rodLength: Double) : Engine {  <b>(1)</b>

    override val cylinders = 8

    override fun start(): String {
        return "Starting V$cylinders [rodLength=$rodLength]"
    }
}

@Factory
class EngineFactory {

    private var engine: V8Engine? = null
    private var rodLength = 5.7

    @PostConstruct
    fun initialize() {
        engine = V8Engine(rodLength) <b>(2)</b>
    }

    @Singleton
    fun v8Engine(): Engine? {
        return engine<b>(3)</b>
    }

    fun setRodLength(rodLength: Double) {
        this.rodLength = rodLength
    }
}

@Singleton
class EngineInitializer : BeanInitializedEventListener<EngineFactory> { <b>(4)</b>
    override fun onInitialized(event: BeanInitializingEvent<EngineFactory>): EngineFactory {
        val engineFactory = event.bean
        engineFactory.setRodLength(6.6)<b>(5)</b>
        return event.bean as EngineFactory
    }
}
1 The V8Engine class defines a rodLength property
2 The EngineFactory initializes the value of rodLength and creates the instance
3 The created instance is returned as aBean
4 The BeanInitializedEventListener interface is implemented to listen for the initialization of the factory
5 Within the onInitialized method the rodLength is overridden prior to the engine being created by the factory bean.

The BeanCreatedEventListener interface is more typically used to decorate or enhance a fully initialized bean by creating a proxy for example.

Bean event listeners are initialized before type converters. If your event listener relies on type conversion either by relying on a configuration properties bean or by any other mechanism, you may see errors related to type conversion.

3.14 Bean Introspection

Since Micronaut 1.1, a compilation time replacement for the JDK’s Introspector class has been included.

TheBeanIntrospector andBeanIntrospection interfaces allow looking up bean introspections that allow you to instantiate and read/write bean properties without using reflection or caching reflective metadata which consumes excessive memory for large beans.

Making a Bean Available for Introspection

Unlike the JDK’s Introspector every class is not automatically available for introspection, to make a class available for introspection you must as a minimum enable Micronaut’s annotation processor ( micronaut-inject-java for Java and Kotlin and micronaut-inject-groovy for Groovy) in your build and ensure you have a runtime time dependency on micronaut-core .

annotationProcessor("io.micronaut:micronaut-inject-java:2.0.0")
<annotationProcessorPaths>
    <path>
        <groupId>io.micronaut</groupId>
        <artifactId>micronaut-inject-java</artifactId>
        <version>2.0.0</version>
    </path>
</annotationProcessorPaths>
For Kotlin the micronaut-inject-java dependency should be in kapt scope and for Groovy you should have micronaut-inject-groovy in compileOnly scope.
runtime("io.micronaut:micronaut-core:2.0.0")
<dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-core</artifactId>
    <version>2.0.0</version>
    <scope>runtime</scope>
</dependency>

Once your build is configured you have a few ways to generate introspection data.

Use the @Introspected Annotation

The@Introspected annotation can be used on any class which you want to make available for introspection, simply annotate the class with@Introspected:

import io.micronaut.core.annotation.Introspected;

@Introspected
public class Person {

    private String name;
    private int age = 18;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
import groovy.transform.Canonical
import io.micronaut.core.annotation.Introspected

@Introspected
@Canonical
class Person {

    String name
    int age = 18

    Person(String name) {
        this.name = name
    }
}
import io.micronaut.core.annotation.Introspected

@Introspected
data class Person(var name : String) {
    var age : Int = 18
}

Once introspection data has been produced at compilation time you can then retrieve it via theBeanIntrospection API:

final BeanIntrospection<Person> introspection = BeanIntrospection.getIntrospection(Person.class); <b>(1)</b>
        Person person = introspection.instantiate("John"); <b>(2)</b>
        System.out.println("Hello " + person.getName());

        final BeanProperty<Person, String> property = introspection.getRequiredProperty("name", String.class); <b>(3)</b>
        property.set(person, "Fred"); <b>(4)</b>
        String name = property.get(person); <b>(5)</b>
        System.out.println("Hello " + person.getName());
def introspection = BeanIntrospection.getIntrospection(Person) <b>(1)</b>
        Person person = introspection.instantiate("John") <b>(2)</b>
        println("Hello ${person.name}")

        BeanProperty<Person, String> property = introspection.getRequiredProperty("name", String) <b>(3)</b>
        property.set(person, "Fred") <b>(4)</b>
        String name = property.get(person) <b>(5)</b>
        println("Hello ${person.name}")
val introspection = BeanIntrospection.getIntrospection(Person::class.java) <b>(1)</b>
        val person : Person = introspection.instantiate("John") <b>(2)</b>
        print("Hello ${person.name}")

        val property : BeanProperty<Person, String> = introspection.getRequiredProperty("name", String::class.java) <b>(3)</b>
        property.set(person, "Fred") <b>(4)</b>
        val name = property.get(person) <b>(5)</b>
        print("Hello ${person.name}")
1 You can retrieve aBeanIntrospection with the getIntrospection static method
2 Once you have aBeanIntrospection you can instantiate a bean with the instantiate method.
3 ABeanProperty can be retreived from the introspection
4 .. and the set method used to set the property value
5 .. and the get method used to retrieve the property value

Constructor Methods

For classes with multiple constructors, it is possible to inform Micronaut which constructor should be used to instantiate the class. Apply the@Creator annotation to which constructor to be used.

import io.micronaut.core.annotation.Creator;
import io.micronaut.core.annotation.Introspected;

import javax.annotation.concurrent.Immutable;

@Introspected
@Immutable
public class Vehicle {

    private final String make;
    private final String model;
    private final int axels;

    public Vehicle(String make, String model) {
        this(make, model, 2);
    }

    @Creator <b>(1)</b>
    public Vehicle(String make, String model, int axels) {
        this.make = make;
        this.model = model;
        this.axels = axels;
    }

    public String getMake() {
        return make;
    }

    public String getModel() {
        return model;
    }

    public int getAxels() {
        return axels;
    }
}
import io.micronaut.core.annotation.Creator
import io.micronaut.core.annotation.Introspected

import javax.annotation.concurrent.Immutable

@Introspected
@Immutable
class Vehicle {

    private final String make
    private final String model
    private final int axels

    Vehicle(String make, String model) {
        this(make, model, 2)
    }

    @Creator <b>(1)</b>
    Vehicle(String make, String model, int axels) {
        this.make = make
        this.model = model
        this.axels = axels
    }

    String getMake() {
        make
    }

    String getModel() {
        model
    }

    int getAxels() {
        axels
    }
}
import io.micronaut.core.annotation.Creator
import io.micronaut.core.annotation.Introspected

import javax.annotation.concurrent.Immutable

@Introspected
@Immutable
class Vehicle @Creator constructor(val make: String, val model: String, val axels: Int) { <b>(1)</b>

    constructor(make: String, model: String) : this(make, model, 2) {}
}
1 The@Creator annotation is used to denote which constructor should be used
This class has no default constructor so calls to instantiate without arguments will throw an InstantiationException .

Static Creator Methods

For the use case of static methods being used to instantiate the class, the@Creator annotation can be applied to those methods to allow for the introspection to execute the method.

import io.micronaut.core.annotation.Creator;
import io.micronaut.core.annotation.Introspected;

import javax.annotation.concurrent.Immutable;

@Introspected
@Immutable
public class Business {

    private String name;

    private Business(String name) {
        this.name = name;
    }

    @Creator <b>(1)</b>
    public static Business forName(String name) {
        return new Business(name);
    }

    public String getName() {
        return name;
    }

}
import io.micronaut.core.annotation.Creator
import io.micronaut.core.annotation.Introspected

import javax.annotation.concurrent.Immutable

@Introspected
@Immutable
class Business {

    private String name

    private Business(String name) {
        this.name = name
    }

    @Creator <b>(1)</b>
    static Business forName(String name) {
        new Business(name)
    }

    String getName() {
        name
    }

}
import io.micronaut.core.annotation.Creator
import io.micronaut.core.annotation.Introspected

import javax.annotation.concurrent.Immutable

@Introspected
@Immutable
class Business private constructor(val name: String) {
    companion object {

        @Creator <b>(1)</b>
        fun forName(name: String): Business {
            return Business(name)
        }
    }

}
1 The@Creator annotation is applied to the static method which will be used to instantiate the class
There can be multiple "creator" methods annotated. If one exists without arguments, that will be used as the default construction method. The first method with arguments will be used as the primary construction method.

Enums

It is possible to introspect enums as well. Simply add the annotation to the enum and it can be constructed through the standard valueOf method.

Use the @Introspected Annotation on a Configuration Class

If the class you wish to introspect is already compiled and not under your control an alternative option is to define a configuration class with the classes member of the@Introspected annotation set.

import io.micronaut.core.annotation.Introspected;

@Introspected(classes = Person.class)
public class PersonConfiguration {
}
import io.micronaut.core.annotation.Introspected

@Introspected(classes = Person)
class PersonConfiguration {
}
import io.micronaut.core.annotation.Introspected

@Introspected(classes = [Person::class])
class PersonConfiguration

In the above example the PersonConfiguration class will generate introspections for the Person class.

You can also use the packages member of the@Introspected which will package scan at compilation time and generate introspections for all classes within a package. Note however this feature is currently regarded as experimental.

Write an AnnotationMapper to Introspect Existing Annotations

If there is an existing annotation that you wish to introspect by default you can write anAnnotationMapper.

An example of this is EntityIntrospectedAnnotationMapper which ensures all beans annotated with javax.persistence.Entity are introspectable by default.

The AnnotationMapper should be on the annotation processor classpath.

The BeanWrapper API

ABeanProperty provides raw access to read and write a property value for a given class and does not provide any automatic type conversion.

It is expected that the values you pass to the set and get methods match the underlying property type otherwise an exception will occur.

To provide additional type conversion smarts theBeanWrapper interface allows wrapping an existing bean instance and setting and getting properties from the bean, plus performing type conversion as necessary.

final BeanWrapper<Person> wrapper = BeanWrapper.getWrapper(new Person("Fred")); <b>(1)</b>

        wrapper.setProperty("age", "20"); <b>(2)</b>
        int newAge = wrapper.getRequiredProperty("age", int.class); <b>(3)</b>

        System.out.println("Person's age now " + newAge);
final BeanWrapper<Person> wrapper = BeanWrapper.getWrapper(new Person("Fred")) <b>(1)</b>

        wrapper.setProperty("age", "20") <b>(2)</b>
        int newAge = wrapper.getRequiredProperty("age", Integer) <b>(3)</b>

        println("Person's age now $newAge")
val wrapper = BeanWrapper.getWrapper(Person("Fred")) <b>(1)</b>

        wrapper.setProperty("age", "20") <b>(2)</b>
        val newAge = wrapper.getRequiredProperty("age", Int::class.java) <b>(3)</b>

        println("Person's age now $newAge")
1 The getWrapper static method can be used to obtain aBeanWrapper for a bean instance.
2 You can set properties and theBeanWrapper will perform type conversion, or throw ConversionErrorException if conversion is not possible.
3 You can retrieve a property using getRequiredProperty and request the appropriate type. If the property doesn’t exist a IntrospectionException will be thrown and if it cannot be converted a ConversionErrorException will be thrown.

Jackson and Bean Introspection

You can enable bean introspection integration with Jackson for reflection-free JSON serialization and deserialization using the jackson.bean-introspection-module setting.

This feature is currently regarded as experimental and may be subject to changes in the future.

Enabling Jackson Bean Introspection

jackson:
    bean-introspection-module: true

With the above configuration in place Jackson will be configured to use theBeanIntrospection API to read and write property values and construct objects resulting in reflection-free serialization/deserialization which is benefitial from a performance perspective and requires less configuring to operate correctly with runtimes such as GraalVM native.

Currently only bean properties (private field with public getter/setter) are supported and usage of public fields is not supported.

3.15 Bean Validation

Since Micronaut 1.2, Micronaut has built-in support for validating beans that are annotated with javax.validation annotations. As a minimum you should include the micronaut-validation module as a compile dependency:

implementation("io.micronaut:micronaut-validation")
<dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-validation</artifactId>
</dependency>

Note that Micronaut’s implementation is not currently fully compliant with the Bean Validator specification as the specification heavily relies on reflection-based APIs.

The following features are unsupported at this time:

  • Annotations on generic argument types since only the Java language supports this feature.

  • Any interaction with the constraint metadata API , since Micronaut uses already computed compilation time metadata.

  • XML-based configuration

  • Instead of using javax.validation.ConstraintValidator you should useConstraintValidator (io.micronaut.validation.validator.constraints.ConstraintValidator) to define custom constraints, which supports validating annotations at compilation time.

Micronaut’s implementation includes the following benefits:

  • Reflection and Runtime Proxy free validation resulting in reduced memory consumption

  • Smaller JAR size since Hibernate Validator adds another 1.4MB

  • Faster startup since Hibernate Validator adds 200ms+ startup overhead

  • Configurability via Annotation Metadata

  • Support for Reactive Bean Validation

  • Support for validating the source AST at compilation time

  • Automatic compatibility with GraalVM native without additional configuration

If you require full Bean Validator 2.0 compliance you can add the micronaut-hibernate-validator module to your classpath, which will replace Micronaut’s implementation.

implementation("io.micronaut.beanvalidation:micronaut-hibernate-validator")
<dependency>
    <groupId>io.micronaut.beanvalidation</groupId>
    <artifactId>micronaut-hibernate-validator</artifactId>
</dependency>

Validating Bean Methods

You can validate methods of any class declared as a Micronaut bean simply by using the javax.validation annotation as arguments:

import javax.inject.Singleton;
import javax.validation.constraints.NotBlank;

@Singleton
public class PersonService {
    public void sayHello(@NotBlank String name) {
        System.out.println("Hello " + name);
    }
}
import javax.inject.Singleton
import javax.validation.constraints.NotBlank

@Singleton
class PersonService {

    void sayHello(@NotBlank String name) {
        println "Hello $name"
    }
}
import javax.inject.Singleton
import javax.validation.constraints.NotBlank


@Singleton
open class PersonService {
    open fun sayHello(@NotBlank name: String) {
        println("Hello $name")
    }
}

The above example declares that the @NotBlank annotation should be validated when executing the sayHello method.

If you are using Kotlin the class and method must be declared open so that Micronaut can create a compilation time subclass, alternatively you can annotate the class with@Validated and configure the Kotlin all-open plugin to open classes annotated with this type. See the Compiler plugins section.

If a validation error occurs a javax.validation.ConstraintViolationException will be thrown. For example:

import io.micronaut.test.annotation.MicronautTest;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

import javax.inject.Inject;
import javax.validation.ConstraintViolationException;

@MicronautTest
class PersonServiceSpec {

    @Inject PersonService personService;

    @Test
    void testThatNameIsValidated() {
        final ConstraintViolationException exception =
                assertThrows(ConstraintViolationException.class, () ->
                personService.sayHello("") <b>(1)</b>
        );

        assertEquals("sayHello.name: must not be blank", exception.getMessage()); <b>(2)</b>
    }
}
import io.micronaut.test.annotation.MicronautTest
import spock.lang.Specification

import javax.inject.Inject
import javax.validation.ConstraintViolationException

@MicronautTest
class PersonServiceSpec extends Specification {

    @Inject PersonService personService

    void "test person name is validated"() {
        when:"The sayHello method is called with a blank string"
        personService.sayHello("") <b>(1)</b>

        then:"A validation error occurs"
        def e = thrown(ConstraintViolationException)
        e.message == "sayHello.name: must not be blank" //  <b>(2)</b>
    }
}
import io.micronaut.test.annotation.MicronautTest
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Assertions.*

import javax.inject.Inject
import javax.validation.ConstraintViolationException

@MicronautTest
class PersonServiceSpec {

    @Inject
    lateinit var personService: PersonService

    @Test
    fun testThatNameIsValidated() {
        val exception = assertThrows(ConstraintViolationException::class.java
        ) { personService.sayHello("") } <b>(1)</b>

        assertEquals("sayHello.name: must not be blank", exception.message) <b>(2)</b>
    }
}
1 The method is called with a blank string
2 An exception occurs

Validating Data Classes

If you wish to validate data classes, such as POJOs and so on, typically used in JSON interchange, the class must be annotated with@Introspected (see the previous section on) or, if the class is external, be imported by the @Introspected annotation.

import io.micronaut.core.annotation.Introspected;
import javax.validation.constraints.*;

@Introspected
public class Person {
    private String name;
    @Min(18)
    private int age;

    @NotBlank
    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
import io.micronaut.core.annotation.Introspected

import javax.validation.constraints.Min
import javax.validation.constraints.NotBlank

@Introspected
class Person {
    @NotBlank
    String name
    @Min(18l)
    int age
}
import io.micronaut.core.annotation.Introspected
import javax.validation.constraints.Min
import javax.validation.constraints.NotBlank

@Introspected
data class Person(
    @field:NotBlank var name: String,
    @field:Min(18) var age: Int
)
The@Introspected annotation can be used as a meta-annotation and common annotations like @javax.persistence.Entity are treated as @Introspected

The above example defines a class called Person that has 2 properties ( name and age ) that have constraints applied to them. Note that in Java the annotations can be on the field or the getter and with Kotlin data classes the annotation should target the field.

If you wish to validate the class manually then you can inject an instance ofValidator:

@Inject
Validator validator;

@Test
void testThatPersonIsValidWithValidator() {
    Person person = new Person();
    person.setName("");
    person.setAge(10);

    final Set<ConstraintViolation<Person>> constraintViolations = validator.validate(person);  <b>(1)</b>

    assertEquals(2, constraintViolations.size()); <b>(2)</b>
}
@Inject Validator validator

void "test person is validated with validator"() {
    when:"The person is validated"
    def constraintViolations = validator.validate(new Person(name: "", age: 10)) <b>(1)</b>

    then:"A validation error occurs"
    constraintViolations.size() == 2 //  <b>(2)</b>
}
@Inject
lateinit var validator: Validator

@Test
fun testThatPersonIsValidWithValidator() {
    val person = Person("", 10)
    val constraintViolations = validator.validate(person)  <b>(1)</b>

    assertEquals(2, constraintViolations.size) <b>(2)</b>
}
1 The validator is used to validate the person
2 The constraint violations are verified

Alternatively on Bean methods you can use javax.validation.Valid to trigger cascading validation:

@Singleton
public class PersonService {
    public void sayHello(@Valid Person person) {
        System.out.println("Hello " + person.getName());
    }
}
@Singleton
class PersonService {
    void sayHello(@Valid Person person) {
        println "Hello $person.name"
    }
}
@Singleton
open class PersonService {
    open fun sayHello(@Valid person: Person) {
        println("Hello ${person.name}")
    }
}

The PersonService will now validate the Person class when invoked:

@Inject
PersonService personService;

@Test
void testThatPersonIsValid() {
    Person person = new Person();
    person.setName("");
    person.setAge(10);

    final ConstraintViolationException exception =
            assertThrows(ConstraintViolationException.class, () ->
                    personService.sayHello(person) <b>(1)</b>
            );

    assertEquals(2, exception.getConstraintViolations().size()); <b>(2)</b>
}
@Inject PersonService personService

void "test person name is validated"() {
    when:"The sayHello method is called with an invalid person"
    personService.sayHello(new Person(name: "", age: 10)) <b>(1)</b>

    then:"A validation error occurs"
    def e = thrown(ConstraintViolationException)
    e.constraintViolations.size() == 2 //  <b>(2)</b>
}
@Inject
lateinit var personService: PersonService

@Test
fun testThatPersonIsValid() {
    val person = Person("", 10)
    val exception = assertThrows(ConstraintViolationException::class.java  <b>(1)</b>
    ) { personService.sayHello(person) }

    assertEquals(2, exception.constraintViolations.size) <b>(2)</b>
}
1 A validated method is invoked
2 The constraint violations are verified

Validating Configuration Properties

You can also validate the properties of classes that are annotated with @ConfigurationProperties to ensure configuration is correct.

It is recommended that you annotate @ConfigurationProperties that features validation with@Context to ensure that the validation occurs at startup time.

Defining Additional Constraints

To define additional constraints you can define a new annotation, for example:

import javax.validation.Constraint;
import java.lang.annotation.*;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = { }) <b>(1)</b>
public @interface DurationPattern {

    String message() default "invalid duration ({validatedValue})"; <b>(2)</b>

    /**
     * Defines several constraints on the same element.
     */
    @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
    @Retention(RUNTIME)
    @Documented
    @interface List {
        DurationPattern[] value(); <b>(3)</b>
    }
}
import javax.validation.Constraint
import java.lang.annotation.*

import static java.lang.annotation.RetentionPolicy.RUNTIME

@Retention(RUNTIME)
@Constraint(validatedBy = []) <b>(1)</b>
@interface DurationPattern {
    String message() default "invalid duration ({validatedValue})" <b>(2)</b>
}
import javax.validation.Constraint

@Retention(AnnotationRetention.RUNTIME)
@Constraint(validatedBy = []) <b>(1)</b>
annotation class DurationPattern(
    val message: String = "invalid duration ({validatedValue})" <b>(2)</b>
)
1 The annotation should be annotated with javax.validation.Constraint
2 A message template can be provided in a hard coded manner as above. If none is specified Micronaut will try to find a message using ClassName.message using theMessageSource interface. (optional)
3 To support repeated annotations you can define a inner annotation (optional).
You can add messages and message bundles using theMessageSource and ResourceBundleMessageSource classes.

Once you have defined the annotation you need to implement aConstraintValidator that validates the annotation. You can either implement a bean that implements the interface directly or define a factory that returns one or more validators.

The latter approach is recommended if you plan to define multiple validators:

import io.micronaut.context.annotation.Factory;
import io.micronaut.validation.validator.constraints.ConstraintValidator;
import javax.inject.Singleton;

@Factory
public class MyValidatorFactory {

    @Singleton
    ConstraintValidator<DurationPattern, CharSequence> durationPatternValidator() {
        return (value, annotationMetadata, context) ->
                value == null || value.toString().matches("^PT?[\\d]+[SMHD]{1}$");
    }
}
import io.micronaut.context.annotation.Factory
import io.micronaut.core.annotation.AnnotationValue
import io.micronaut.validation.validator.constraints.*
import javax.inject.Singleton

@Factory
class MyValidatorFactory {

    @Singleton
    ConstraintValidator<DurationPattern, CharSequence> durationPatternValidator() {
        return { CharSequence value,
                 AnnotationValue<DurationPattern> annotation,
                 ConstraintValidatorContext context ->
            return value == null || value.toString() ==~ /^PT?[\d]+[SMHD]{1}$/
        } as ConstraintValidator<DurationPattern, CharSequence>
    }
}
import io.micronaut.context.annotation.Factory
import io.micronaut.validation.validator.constraints.ConstraintValidator
import javax.inject.Singleton

@Factory
class MyValidatorFactory {

    @Singleton
    fun durationPatternValidator() : ConstraintValidator<DurationPattern, CharSequence> {
        return ConstraintValidator { value, annotation, context ->
            value == null || value.toString().matches("^PT?[\\d]+[SMHD]{1}$".toRegex())
        }
    }
}

The above example implements a validator that validates any field, parameter etc. that is annotated with DurationPattern , ensuring that the string can be parsed with java.time.Duration.parse .

Generally null is regarded as valid and @NotNull used to constrain a value as not being null . The example above regards null as a valid value.

For example:

@Singleton
public class HolidayService {

    public String startHoliday(@NotBlank String person,
                        @DurationPattern String duration) {
        final Duration d = Duration.parse(duration);
        return "Person " + person + " is off on holiday for " + d.toMinutes() + " minutes";
    }
}
@Singleton
class HolidayService {

    String startHoliday(@NotBlank String person,
                        @DurationPattern String duration) {
        final Duration d = Duration.parse(duration)
        return "Person $person is off on holiday for ${d.toMinutes()} minutes"
    }
}
@Singleton
open class HolidayService {

    open fun startHoliday( @NotBlank person: String,
                           @DurationPattern duration: String): String {
        val d = Duration.parse(duration)
        val mins = d.toMinutes()
        return "Person $person is off on holiday for $mins minutes"
    }
}

To verify the above examples validates the duration parameter you can define a test:

@Inject HolidayService holidayService;

@Test
void testCustomValidator() {
    final ConstraintViolationException exception =
            assertThrows(ConstraintViolationException.class, () ->
                    holidayService.startHoliday("Fred", "junk")   <b>(1)</b>
            );

    assertEquals("startHoliday.duration: invalid duration (junk)", exception.getMessage()); <b>(2)</b>
}
void "test test custom validator"() {
    when:"A custom validator is used"
    holidayService.startHoliday("Fred", "junk") <b>(1)</b>

    then:"A validation error occurs"
    def e = thrown(ConstraintViolationException)
    e.message == "startHoliday.duration: invalid duration (junk)" //  <b>(2)</b>
}
@Inject
lateinit var holidayService: HolidayService

@Test
fun testCustomValidator() {
    val exception = assertThrows(ConstraintViolationException::class.java
    ) { holidayService.startHoliday("Fred", "junk") }   <b>(1)</b>

    assertEquals("startHoliday.duration: invalid duration (junk)", exception.message) <b>(2)</b>
}
1 A validated method is invoked
2 THe constraint violations are verified

Validating Annotations at Compilation Time

You can use Micronaut’s validator to validate annotation usages at compilation time. To do so you should include micronaut-validation in the annotation processor classpath:

annotationProcessor("io.micronaut:micronaut-validation")
<annotationProcessorPaths>
    <path>
        <groupId>io.micronaut</groupId>
        <artifactId>micronaut-validation</artifactId>
    </path>
</annotationProcessorPaths>

Once this is done Micronaut will at compilation validate annotation values that are themselves annotated with javax.validation . For example consider the following annotation:

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
public @interface TimeOff {
    @DurationPattern
    String duration();
}
import java.lang.annotation.*

@Retention(RetentionPolicy.RUNTIME)
@interface TimeOff {
    @DurationPattern
    String duration()
}
@Retention(AnnotationRetention.RUNTIME)
annotation class TimeOff(
    @DurationPattern val duration: String
)

If your attempt to use @TimeOff(duration="junk") in your source Micronaut will fail compilation due to the value of duration violating the DurationPattern constraint.

If duration is a property placeholder such as @TimeOff(duration="${my.value}") then validation handling will be deferred until runtime.

Note that if you wish to allow use of a custom ConstraintValidator at compilation time you should instead define the validator as a class:

import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.validation.validator.constraints.*;

public class DurationPatternValidator implements ConstraintValidator<DurationPattern, CharSequence> {
    @Override
    public boolean isValid(
            @Nullable CharSequence value,
            @NonNull AnnotationValue<DurationPattern> annotationMetadata,
            @NonNull ConstraintValidatorContext context) {
        return value == null || value.toString().matches("^PT?[\\d]+[SMHD]{1}$");
    }
}
import edu.umd.cs.findbugs.annotations.NonNull
import edu.umd.cs.findbugs.annotations.Nullable
import io.micronaut.core.annotation.AnnotationValue
import io.micronaut.validation.validator.constraints.*

class DurationPatternValidator implements ConstraintValidator<DurationPattern, CharSequence> {
    @Override
    boolean isValid(
            @Nullable CharSequence value,
            @NonNull AnnotationValue<DurationPattern> annotationMetadata,
            @NonNull ConstraintValidatorContext context) {
        return value == null || value.toString() ==~ /^PT?[\d]+[SMHD]{1}$/
    }
}
import io.micronaut.core.annotation.AnnotationValue
import io.micronaut.validation.validator.constraints.*

class DurationPatternValidator : ConstraintValidator<DurationPattern, CharSequence> {
    override fun isValid(
            value: CharSequence?,
            annotationMetadata: AnnotationValue<DurationPattern>,
            context: ConstraintValidatorContext): Boolean {
        return value == null || value.toString().matches("^PT?[\\d]+[SMHD]{1}$".toRegex())
    }
}

In addition to the following requirements:

  • Define a META-INF/services/io.micronaut.validation.validator.constraints.ConstraintValidator file that references the class.

  • The class should be public and feature a zero argument public constructor

  • The class should be placed on the annotation processor classpath of the project that is to be validated.

3.16 Bean Annotation Metadata

The methods provided by Java’s AnnotatedElement API in general don’t provide the ability to introspect annotations without loading the annotations themselves, nor do they provide any ability to introspect annotation stereotypes (Often called meta-annotations, an annotation stereotype is where an annotation is annotated with another annotation, essentially inheriting its behaviour).

To solve this problem many frameworks produce runtime metadata or perform expensive reflection to analyze the annotations of a class.

Micronaut instead produces this annotation metadata at compile time, avoiding expensive reflection and saving on memory.

TheBeanContext API can be used to obtain a reference to aBeanDefinition which implements theAnnotationMetadata interface.

For example the following code will obtain all bean definitions annotated with a particular stereotype:

Lookup Bean Definitions by Stereotype

BeanContext beanContext = ... // obtain the bean context
Collection<BeanDefinition> definitions =
    beanContext.getBeanDefinitions(Qualifiers.byStereotype(Controller.class))

for(BeanDefinition definition : definitions) {
    AnnotationValue<Controller> controllerAnn = definition.getAnnotation(Controller.class);
    // do something with the annotation
}

The above example will find allBeanDefinition 's annotated with @Controller regardless whether @Controller is used directly or inherited via an annotation stereotype.

Note that the getAnnotation method and the variations of the method return anAnnotationValue type and not a Java annotation. This is by design, and you should generally try to work with this API when reading annotation values, the reason being that synthesizing a proxy implementation is worse from a performance and memory consumption perspective.

If you absolutely require a reference to an annotation instance you can use the synthesize method, which will create a runtime proxy that implements the annotation interface:

Synthesizing Annotation Instances

Controller controllerAnn = definition.synthesize(Controller.class);

This approach is not recommended however, as it requires reflection and increases memory consumption due to the use of runtime created proxies and should be used as a last resort (for example if you need an instance of the annotation to integrate with a third party library).

Aliasing / Mapping Annotations

There are times when you may want to alias the value of an annotation member to the value of another annotation member. To do this you can use the@AliasFor annotation to alias the value of one member to the value of another.

A common use case is for example when an annotation defines the value() member, but also supports other members. For example the@Client annotation:

The @Client Annotation

public @interface Client {

    /**
     * @return The URL or service ID of the remote service
     */
    @AliasFor(member = "id") <i data-value="1"></i><b>(1)</b>
    String value() default "";

    /**
     * @return The ID of the client
     */
    @AliasFor(member = "value") <i data-value="2"></i><b>(2)</b>
    String id() default "";
}
1 The value member also sets the id member
2 The id member also sets the value member

With these aliases in place, regardless whether you define @Client("foo") or @Client(id="foo") both the value and id members are always set, making it much easier to parse and deal with the annotation.

If you do not have control over the annotation then another approach is to use anAnnotationMapper. To create an AnnotationMapper you must perform the following steps:

  • Implement theAnnotationMapper interface

  • Define a META-INF/services/io.micronaut.inject.annotation.AnnotationMapper file referencing the implementation class

  • Add the JAR file containing the implementation to the annotationProcessor classpath ( kapt for Kotlin)

Because AnnotationMapper implementations need to be on the annotation processor classpath they should generally be in a project that includes few external dependencies to avoid polluting the annotation processor classpath.

The following is an example the AnnotationMapper that improves the introspection capabilities of JPA entities

EntityIntrospectedAnnotationMapper Mapper Example

public class EntityIntrospectedAnnotationMapper implements NamedAnnotationMapper {
    @NonNull
    @Override
    public String getName() {
        return "javax.persistence.Entity";
    }

    @Override
    public List<AnnotationValue<?>> map(AnnotationValue<Annotation> annotation, VisitorContext visitorContext) { <i data-value="1"></i><b>(1)</b>
        final AnnotationValueBuilder<Introspected> builder = AnnotationValue.builder(Introspected.class)
                // don't bother with transients properties
                .member("excludedAnnotations", "javax.persistence.Transient"); <i data-value="2"></i><b>(2)</b>
        return Collections.singletonList(
                builder.build()
        );
    }
}
1 The map method receives aAnnotationValue with the values for the annotation.
2 One or more annotations can be returned, in this case @Transient .
The example above implements the NamedAnnotationMapper interface which allows for annotations to be mixed with runtime code. If you want to operate against a concrete annotation type then you should use TypedAnnotationMapper instead, though note it requires the annotation class itself to be on the annotation processor classpath.

3.17 Micronaut Beans And Spring

The MicronautBeanProcessor class is a BeanFactoryPostProcessor which will add Micronaut beans to a Spring Application Context. An instance of MicronautBeanProcessor should be added to the Spring Application Context. MicronautBeanProcessor requires a constructor parameter which represents a list of the types of Micronaut beans which should be added to the Spring Application Context. The processor may be used in any Spring application. As an example, a Grails 3 application could take advantage of MicronautBeanProcessor to add all of the Micronaut HTTP Client beans to the Spring Application Context with something like the folowing:

// grails-app/conf/spring/resources.groovy
import io.micronaut.spring.beans.MicronautBeanProcessor
import io.micronaut.http.client.annotation.Client

beans = {
    httpClientBeanProcessor MicronautBeanProcessor, Client
}

Multiple types may be specified:

// grails-app/conf/spring/resources.groovy
import io.micronaut.spring.beans.MicronautBeanProcessor
import io.micronaut.http.client.annotation.Client
import com.sample.Widget

beans = {
    httpClientBeanProcessor MicronautBeanProcessor, [Client, Widget]
}

In a non-Grails application something similar may be specified using any of Spring’s bean definition styles:

Unresolved directive in <stdin> - include::spring/src/test/groovy/io/micronaut/spring/beans/MicronautBeanProcessorByAnnotationTypeSpec.groovy[tags=springconfig, indent=0]

3.18 Android Support

Since Micronaut dependency injection is based on annotation processors and doesn’t rely on reflection, it can be used on Android when using the Android plugin 3.0.0 or above.

This allows you to use the same application framework for both your Android client and server implementation.

Configuring Your Android Build

To get started you must add the Micronaut annotation processors to the processor classpath using the annotationProcessor dependency configuration.

The Micronaut micronaut-inject-java dependency should be included in both the annotationProcessor and compileOnly scopes of your Android build configuration:

Example Android build.gradle

dependencies {
    ...
    annotationProcessor "io.micronaut:micronaut-inject-java:2.0.0"
    compileOnly "io.micronaut:micronaut-inject-java:2.0.0"
    ...
}

If you use lint as part of your build you may also need to disable the invalid packages check since Android includes a hard coded check that regards the javax.inject package as invalid unless you are using Dagger:

Configure lint within build.gradle

android {
    ...
    lintOptions {
        lintOptions { warning 'InvalidPackage' }
    }
}

You can find more information on configuring annotations processors in the Android documentation.

Micronaut inject-java dependency uses Android Java 8 support features.

Enabling Dependency Injection

Once you have configured the classpath correctly, the next step is start theApplicationContext.

The following example demonstrates creating a subclass of android.app.Application for that purpose:

Example Android Application Class

import android.app.Activity;
import android.app.Application;
import android.os.Bundle;

import io.micronaut.context.ApplicationContext;
import io.micronaut.context.env.Environment;

public class BaseApplication extends Application { <i data-value="1"></i><b>(1)</b>

    private ApplicationContext ctx;

    public BaseApplication() {
        super();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        ctx = ApplicationContext.run(MainActivity.class, Environment.ANDROID); <i data-value="2"></i><b>(2)</b>
        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { <i data-value="3"></i><b>(3)</b>
            @Override
            public void onActivityCreated(Activity activity, Bundle bundle) {
                ctx.inject(activity);
            }
            ... // shortened for brevity, it is not necessary to implement other methods
        });
    }
}
1 Extend the android.app.Application class
2 Run the ApplicationContext with the ANDROID environment
3 To allow dependency injection of Android Activity instances register a ActivityLifecycleCallbacks instance

4 Application Configuration

Configuration in Micronaut takes inspiration from both Spring Boot and Grails, integrating configuration properties from multiple sources directly into the core IoC container.

Configuration can by default be provided in either Java properties, YAML, JSON or Groovy files. The convention is to search for a file called application.yml , application.properties , application.json or application.groovy .

In addition, just like Spring and Grails, Micronaut allows overriding any property via system properties or environment variables.

Each source of configuration is modeled with thePropertySource interface and the mechanism is extensible allowing the implementation of additionalPropertySourceLoader implementations.

4.1 The Environment

The application environment is modelled by theEnvironment interface, which allows specifying one or many unique environment names when creating anApplicationContext.

Initializing the Environment

ApplicationContext applicationContext = ApplicationContext.run("test", "android");
        Environment environment = applicationContext.getEnvironment();

        assertTrue(environment.getActiveNames().contains("test"));
        assertTrue(environment.getActiveNames().contains("android"));

Initializing the Environment

when:
        ApplicationContext applicationContext = ApplicationContext.run("test", "android")
        Environment environment = applicationContext.getEnvironment()

        then:
        environment.getActiveNames().contains("test")
        environment.getActiveNames().contains("android")

Initializing the Environment

val applicationContext = ApplicationContext.run("test", "android")
        val environment = applicationContext.getEnvironment()

        assertTrue(environment.getActiveNames().contains("test"))
        assertTrue(environment.getActiveNames().contains("android"))

The active environment names serve the purpose of allowing loading different configuration files depending on the environment and also using the@Requires annotation to conditionally load beans or bean@Configuration packages.

In addition, Micronaut will attempt to detect the current environments. For example within a Spock or JUnit test theTEST environment will be automatically active.

Additional active environments can be specified using the micronaut.environments system property or the MICRONAUT_ENVIRONMENTS environment variable. These can be specified as a comma separated list. For example:

Specifying environments

$ java -Dmicronaut.environments=foo,bar -jar myapp.jar

The above activates environments called foo and bar .

Finally, the Cloud environment names are also detected. See the section onfor more information.

Environment Priority

Micronaut will load property sources based off the environments specified, and if the same property key exists in multiple property sources specific to an environment, the environment order determines which value will be used.

Micronaut uses the following hierarchy for environment processing (lowest priority to highest priority):

  • Deduced environments

  • Environments from the micronaut.environments system property

  • Environments from the MICRONAUT_ENVIRONMENTS environment variable

  • Environments specified explicitly through the application context builder

    This also applies to @MicronautTest(environments = )

Disabling Environment Detection

The automatic detection of environments can be disabled setting the micronaut.env.deduction system property or the MICRONAUT_ENV_DEDUCTION environment variable to false . This will prevent Micronaut from detecting current environments, while still using any environments that are specifically provided as shown above.

Disabling environment detection via system property

$  java -Dmicronaut.env.deduction=false -jar myapp.jar

Alternatively, you can disable environment deduction using the ApplicationContextBuilder 's deduceEnvironment method when setting up your application.

Using ApplicationContextBuilder to disable environment deduction

@Test
    public void testDisableEnvironmentDeductionViaBuilder() {
        ApplicationContext ctx = ApplicationContext.builder().deduceEnvironment(false).start();
        assertFalse(ctx.getEnvironment().getActiveNames().contains(Environment.TEST));
        ctx.close();
    }

Using ApplicationContextBuilder to disable environment deduction

void "test disable environment deduction via builder"() {
        when:
        ApplicationContext ctx = ApplicationContext.builder().deduceEnvironment(false).start()

        then:
        !ctx.environment.activeNames.contains(Environment.TEST)

        cleanup:
        ctx.close()
    }

Using ApplicationContextBuilder to disable environment deduction

"test disable environment deduction via builder"() {
        val ctx = ApplicationContext.builder().deduceEnvironment(false).start()
        assertFalse(ctx.environment.activeNames.contains(Environment.TEST))
        ctx.close()
    }

4.2 Externalized Configuration with PropertySources

AdditionalPropertySource instances can be added to the environment prior to initializing theApplicationContext.

Initializing the Environment

ApplicationContext applicationContext = ApplicationContext.run(
                PropertySource.of(
                        "test",
                        CollectionUtils.mapOf(
                            "micronaut.server.host", "foo",
                            "micronaut.server.port", 8080
                        )
                ),
                "test", "android");
        Environment environment = applicationContext.getEnvironment();

        assertEquals(
                "foo",
                environment.getProperty("micronaut.server.host", String.class).orElse("localhost")
        );

Initializing the Environment

when:
        ApplicationContext applicationContext = ApplicationContext.run(
                PropertySource.of(
                        "test",
                        [
                            "micronaut.server.host": "foo",
                            "micronaut.server.port": 8080
                        ]
                ),
                "test", "android")
        Environment environment = applicationContext.getEnvironment()

        then:
        "foo" == environment.getProperty("micronaut.server.host", String.class).orElse("localhost")

Initializing the Environment

val applicationContext = ApplicationContext.run(
                PropertySource.of(
                        "test",
                        mapOf(
                                "micronaut.server.host" to "foo",
                                "micronaut.server.port" to 8080
                        )
                ),
                "test", "android")
        val environment = applicationContext.getEnvironment()

        assertEquals(
                "foo",
                environment.getProperty("micronaut.server.host", String::class.java).orElse("localhost")
        )

ThePropertySource.of method can be used to create a ProperySource from a map of values.

Alternatively one can register aPropertySourceLoader by creating a META-INF/services/io.micronaut.context.env.PropertySourceLoader containing a reference to the class name of the PropertySourceLoader .

Included PropertySource Loaders

Micronaut by default contains PropertySourceLoader implementations that load properties from the given locations and priority:

  1. Command line arguments

  2. Properties from SPRING_APPLICATION_JSON (for Spring compatibility)

  3. Properties from MICRONAUT_APPLICATION_JSON

  4. Java System Properties

  5. OS environment variables

  6. Configuration files loaded in order from the system property 'micronaut.config.files' or the environment variable MICRONAUT_CONFIG_FILES . The value can be a comma separated list of paths with the last file having precedence. The files can be referenced from the file system as a path or the classpath with a classpath: prefix.

  7. Enviroment-specific properties from application-{environment}.{extension}

  8. Application-specific properties from application.{extension}

.properties , .json , .yml are supported out of the box. For Groovy users .groovy is supported as well.

Property Value Placeholders

Micronaut includes a property placeholder syntax which can be used to reference configuration properties both within configuration values and with any Micronaut annotation (see@Value and the section onConfiguration Injection).

Programmatic usage is also possible via the PropertyPlaceholderResolver interface.

The basic syntax is to wrap a reference to a property in ${…​} . For example in application.yml :

Defining Property Placeholders

myapp:
    endpoint: http://${micronaut.server.host}:${micronaut.server.port}/foo

The above example embeds references to the micronaut.server.host and micronaut.server.port properties.

You can specify default values by defining a value after the : character. For example:

Using Default Values

myapp:
    endpoint: http://${micronaut.server.host:localhost}:${micronaut.server.port:8080}/foo

The above example will default to localhost and port 8080 if no value is found (rather than throwing an exception). Note that if default value itself contains a : character, you should escape it using back ticks:

Using Backticks

myapp:
    endpoint: ${server.address:`http://localhost:8080`}/foo

The above example tries to read a server.address property otherwise fallbacks back to http://localhost:8080 , since the address has a : character we have to escape it with back ticks.

Property Value Binding

Note that these property references should always be in kebab case (lowercase and hyphen-separated) when placing references in code or in placeholder values. For example, you should use micronaut.server.default-charset and not micronaut.server.defaultCharset .

Micronaut still allows specifying the latter in configuration, but normalizes the properties into kebab case form to optimize memory consumption and reduce complexity when resolving properties. The following table summarizes how properties are normalized from different sources:

Table 1. Property Value Normalization
Configuration Value Resulting Properties Property Source

myApp.myStuff

my-app.my-stuff

Properties, YAML etc.

my-app.myStuff

my-app.my-stuff

Properties, YAML etc.

myApp.my-stuff

my-app.my-stuff

Properties, YAML etc.

MYAPP_MYSTUFF

myapp.mystuff , myapp-mystuff

Environment Variable

MY_APP_MY_STUFF

my.app.my.stuff , my.app.my-stuff , my.app-my.stuff , my.app-my-stuff , my-app.my.stuff , my-app.my-stuff , my-app-my.stuff , my-app-my-stuff

Environment Variable

Environment variables are given special treatment to allow the definition of environment variables to be more flexible. Note that there is no way to reference an environment variable with camel-case.

Because the number of properties generated is exponential based on the number of _ characters in the environment variable, it is recommended to refine which, if any, environment variables are included in configuration if the number of environment variables with a large number of underscores is high.

To control how environment properties participate in configuration, call the respective methods on the Micronaut builder.

Application class

import io.micronaut.runtime.Micronaut;

public class Application {

    public static void main(String[] args) {
        Micronaut.build(null)
                .mainClass(Application.class)
                .environmentPropertySource(false)
                //or
                .environmentVariableIncludes("THIS_ENV_ONLY")
                //or
                .environmentVariableExcludes("EXCLUDED_ENV")
                .start();
    }
}

Application class

import io.micronaut.runtime.Micronaut

class Application {

    static void main(String[] args) {
        Micronaut.build()
                .mainClass(Application)
                .environmentPropertySource(false)
                //or
                .environmentVariableIncludes("THIS_ENV_ONLY")
                //or
                .environmentVariableExcludes("EXCLUDED_ENV")
                .start()
    }
}

Application class

import io.micronaut.runtime.Micronaut

object Application {

    @JvmStatic
    fun main(args: Array<String>) {
        Micronaut.build(null)
                .mainClass(Application::class.java)
                .environmentPropertySource(false)
                //or
                .environmentVariableIncludes("THIS_ENV_ONLY")
                //or
                .environmentVariableExcludes("EXCLUDED_ENV")
                .start()
    }
}
The configuration above does not have any impact on property placeholders. It is still possible to reference an environment variable in a placeholder regardless of whether environment configuration is disabled, or even if the specific property is explicitly excluded.

Using Random Properties

You can use random values by using the following properties. These can be used in configuration files as variables like the following.

micronaut:
  application:
    name: myapplication
    instance:
      id: ${random.shortuuid}
Table 2. Random Values
Property Value

random.port

An available random port number

random.int

Random int

random.integer

Random int

random.long

Random long

random.float

Random float

random.shortuuid

Random UUID of only 10 chars in length (Note: As this isn’t full UUID, collision COULD occur)

random.uuid

Random UUID with dashes

random.uuid2

Random UUID without dashes

Fail Fast Property Injection

For beans that inject required properties, the injection and potential failure will not occur until the bean is requested. To verify at startup that the properties exist and can be injected, the bean can be annotated with@Context. Context scoped beans will be injected at startup time and thus will fail at startup time if any required properties are missing or could not be converted to the required type.

To maintain a fast startup time, it is recommended to use this feature as sparingly as possible.

Controlling Log Levels with Properties

Log levels can be configured via properties defined in application.yml (and environment variables) with the log.level prefix:

logger:
    levels:
        foo.bar: ERROR

Note that the ability to control log levels via config is controlled via theLoggingSystem interface. Currently Micronaut ships with a single implementation that allows setting log levels for the Logback library. If another library is chosen you should provide a bean that implements this interface.

4.3 Configuration Injection

You can inject configuration values into beans with Micronaut using the@Value annotation.

Using the @Value Annotation

Consider the following example:

@Value Example

import javax.inject.Singleton;

@Singleton
public class EngineImpl implements Engine {

    @Value("${my.engine.cylinders:6}") <b>(1)</b>
    protected int cylinders;

    @Override
    public int getCylinders() {
        return this.cylinders;
    }

    public String start() {<b>(2)</b>
        return "Starting V" + getCylinders() + " Engine";
    }

}

@Value Example

import javax.inject.Singleton

@Singleton
class EngineImpl implements Engine {

    @Value('${my.engine.cylinders:6}') <b>(1)</b>
    protected int cylinders

    @Override
    int getCylinders() {
        this.cylinders
    }

    String start() { <b>(2)</b>
        "Starting V${cylinders} Engine"
    }
}

@Value Example

import javax.inject.Singleton

@Singleton
class EngineImpl : Engine {

    @Value("\${my.engine.cylinders:6}") <b>(1)</b>
    override var cylinders: Int = 0
        protected set

    override fun start(): String {<b>(2)</b>
        return "Starting V$cylinders Engine"
    }
}
1 The @Value annotation accepts a string that can have embedded placeholder values (the default value can be provided by specifying a value after the colon : character).
2 The injected value can then be used within code.

Note that @Value can also be used to inject a static value, for example the following will inject the number 10:

Static @Value Example

@Value("10")
int number;

However it is definitely more useful when used to compose injected values combining static content and placeholders. For example to setup a URL:

Placeholders with @Value

@Value("http://${my.host}:${my.port}")
URL url;

In the above example the URL is constructed from 2 placeholder properties that must be present in configuration: my.host and my.port .

Remember that to specify a default value in a placeholder expression, you should use the colon : character, however if the default you are trying to specify has a colon then you should escape the value with back ticks. For example:

Placeholders with @Value

@Value("${my.url:`http://foo.com`}")
URL url;

Note that there is nothing special about @Value itself regarding the resolution of property value placeholders.

Due to Micronaut’s extensive support for annotation metadata you can in fact use property placeholder expressions on any annotation. For example, to make the path of a @Controller configurable you can do:

@Controller("${hello.controller.path:/hello}")
class HelloController {
    ...
}

In the above case if hello.controller.path is specified in configuration then the controller will be mapped to the path specified otherwise it will be mapped to /hello .

You can also make the target server for@Client configurable (although service discovery approaches are often better), for example:

@Client("${my.server.url:`http://localhost:8080`}")
interface HelloClient {
    ...
}

In the above example the property my.server.url can be used to configure the client otherwise the client will fallback to a localhost address.

Using the @Property Annotation

Recall that the@Value annotation receives a String value which is a mix of static content and placeholder expressions. This can lead to confusion if you attempt to do the following:

Incorrect usage of @Value
@Value("my.url")
String url;

In the above case the value my.url will be injected and set to the url field and not the value of the my.url property from your application configuration, this is because @Value only resolves placeholders within the value specified to it.

If you wish to inject a specific property name then you may be better off using@Property:

Using @Property

import io.micronaut.context.annotation.Property;

import javax.inject.Inject;
import javax.inject.Singleton;

@Singleton
public class Engine {

    @Property(name = "my.engine.cylinders") <b>(1)</b>
    protected int cylinders; <b>(2)</b>

    private String manufacturer;

    public int getCylinders() {
        return cylinders;
    }

    public String getManufacturer() {
        return manufacturer;
    }

    @Inject
    public void setManufacturer(@Property(name = "my.engine.manufacturer") String manufacturer) { <b>(3)</b>
        this.manufacturer = manufacturer;
    }

}

Using @Property

import io.micronaut.context.annotation.Property

import javax.inject.Singleton

@Singleton
class Engine {

    @Property(name = "my.engine.cylinders") <b>(1)</b>
    protected int cylinders <b>(2)</b>

    @Property(name = "my.engine.manufacturer") <b>(3)</b>
    String manufacturer

    int getCylinders() {
        cylinders
    }
}

Using @Property

import io.micronaut.context.annotation.Property

import javax.inject.Inject
import javax.inject.Singleton


@Singleton
class Engine {

    @field:Property(name = "my.engine.cylinders") <b>(1)</b>
    protected var cylinders: Int = 0 <b>(2)</b>

    @set:Inject
    @setparam:Property(name = "my.engine.manufacturer") <b>(3)</b>
    var manufacturer: String? = null

    fun cylinders(): Int {
        return cylinders
    }
}
1 The my.engine.cylinders property will be resolved from configuration and injected directly into the field.
2 Fields subjected to injection should never be private otherwise expensive reflection must be used
3 The @Property annotation is used to inject through the setter
Because it is not possible to define a default value with @Property , if the value doesn’t exist or cannot be converted to the required type, the bean instantiation will fail.

The above will instead inject the value of the my.url property resolved from application configuration. If the property can not be found in configuration, an exception will be thrown. As with other types of injection, the injection point can also be annotated with @Nullable to make the injection optional.

You can also use this feature to resolve sub maps. For example, consider the following configuration:

Example application.yml configuration
datasources:
    default:
        name: 'mydb'
jpa:
    default:
        properties:
            hibernate:
                hbm2ddl:
                    auto: update
                show_sql: true

If you wish to resolve a flattened map containing only the properties starting with hibernate then you can do so with @Property , for example:

Using @Property
@Property(name = "jpa.default.properties")
Map<String, String> jpaProperties;

The injected map will contain the keys hibernate.hbm2ddl.auto and hibernate.show_sql and their values.

The@MapFormat annotation can be used to customize the injected map depending whether you want nested keys, flat keys and it allows customization of the key style via theStringConvention enum.

4.4 Configuration Properties

You can create type safe configuration by creating classes that are annotated with @ConfigurationProperties .

Micronaut will produce a reflection-free @ConfigurationProperties bean and will also at compile time calculate the property paths to evaluate, greatly improving the speed and efficiency of loading @ConfigurationProperties .

An example of a configuration class can be seen below:

@ConfigurationProperties Example

import io.micronaut.context.annotation.ConfigurationProperties;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;

@ConfigurationProperties("my.engine") <b>(1)</b>
public class EngineConfig {
    public String getManufacturer() {
        return manufacturer;
    }

    public void setManufacturer(String manufacturer) {
        this.manufacturer = manufacturer;
    }

    public int getCylinders() {
        return cylinders;
    }

    public void setCylinders(int cylinders) {
        this.cylinders = cylinders;
    }

    public CrankShaft getCrankShaft() {
        return crankShaft;
    }

    public void setCrankShaft(CrankShaft crankShaft) {
        this.crankShaft = crankShaft;
    }

    @NotBlank <b>(2)</b>
    private String manufacturer = "Ford"; <b>(3)</b>
    @Min(1L)
    private int cylinders;
    private CrankShaft crankShaft = new CrankShaft();

    @ConfigurationProperties("crank-shaft")
    public static class CrankShaft { <b>(4)</b>
        public Optional<Double> getRodLength() {
            return rodLength;
        }

        public void setRodLength(Optional<Double> rodLength) {
            this.rodLength = rodLength;
        }

        private Optional<Double> rodLength = Optional.empty(); <b>(5)</b>
    }
}

@ConfigurationProperties Example

import io.micronaut.context.annotation.ConfigurationProperties

import javax.validation.constraints.Min
import javax.validation.constraints.NotBlank

@ConfigurationProperties('my.engine') <b>(1)</b>
class EngineConfig {

    @NotBlank <b>(2)</b>
    String manufacturer = "Ford" <b>(3)</b>

    @Min(1L)
    int cylinders
    CrankShaft crankShaft = new CrankShaft()

    @ConfigurationProperties('crank-shaft')
    static class CrankShaft { <b>(4)</b>
        Optional<Double> rodLength = Optional.empty() <b>(5)</b>
    }

}

@ConfigurationProperties Example

import io.micronaut.context.annotation.ConfigurationProperties
import java.util.*

import javax.validation.constraints.Min
import javax.validation.constraints.NotBlank

@ConfigurationProperties("my.engine") <b>(1)</b>
class EngineConfig {

    @NotBlank <b>(2)</b>
    var manufacturer = "Ford" <b>(3)</b>
    @Min(1L)
    var cylinders: Int = 0
    var crankShaft = CrankShaft()

    @ConfigurationProperties("crank-shaft")
    class CrankShaft { <b>(4)</b>
        var rodLength: Optional<Double> = Optional.empty() <b>(5)</b>
    }
}
1 The @ConfigurationProperties annotation takes the configuration prefix
2 You can use javax.validation to validate the configuration
3 Default values can be assigned to the property
4 Static inner classes can provided nested configuration
5 Optional configuration values can be wrapped in a java.util.Optional

Once you have prepared a type safe configuration it can simply be injected into your objects like any other bean:

@ConfigurationProperties Dependency Injection

@Singleton
public class EngineImpl implements Engine {
    private final EngineConfig config;

    public EngineImpl(EngineConfig config) {<b>(1)</b>
        this.config = config;
    }

    @Override
    public int getCylinders() {
        return config.getCylinders();
    }

    public String start() {<b>(2)</b>
        return getConfig().getManufacturer() + " Engine Starting V" + getConfig().getCylinders() +
                " [rodLength=" + getConfig().getCrankShaft().getRodLength().orElse(6.0d) + "]";
    }

    public final EngineConfig getConfig() {
        return config;
    }
}

@ConfigurationProperties Dependency Injection

@Singleton
class EngineImpl implements Engine {
    final EngineConfig config

    EngineImpl(EngineConfig config) { <b>(1)</b>
        this.config = config
    }

    @Override
    int getCylinders() {
        config.cylinders
    }

    String start() { <b>(2)</b>
        "${config.manufacturer} Engine Starting V${config.cylinders} [rodLength=${config.crankShaft.rodLength.orElse(6.0d)}]"
    }
}

@ConfigurationProperties Dependency Injection

@Singleton
class EngineImpl(val config: EngineConfig) : Engine {<b>(1)</b>

    override val cylinders: Int
        get() = config.cylinders

    override fun start(): String {<b>(2)</b>
        return "${config.manufacturer} Engine Starting V${config.cylinders} [rodLength=${config.crankShaft.rodLength.orElse(6.0)}]"
    }
}
1 Inject the EngineConfig bean
2 Use the configuration properties

Configuration values can then be supplied from one of thePropertySource instances. For example:

Supply Configuration

LinkedHashMap<String, Object> map = new LinkedHashMap(1);
        map.put("my.engine.cylinders", "8");
        ApplicationContext applicationContext = ApplicationContext.run(map, "test");

        Vehicle vehicle = applicationContext.getBean(Vehicle.class);
        System.out.println(vehicle.start());

Supply Configuration

ApplicationContext applicationContext = ApplicationContext.run(
                ['my.engine.cylinders': '8'],
                "test"
        )

        Vehicle vehicle = applicationContext
                .getBean(Vehicle)
        println(vehicle.start())

Supply Configuration

val map = mapOf( "my.engine.cylinders" to "8")
        val applicationContext = ApplicationContext.run(map, "test")

        val vehicle = applicationContext.getBean(Vehicle::class.java)
        println(vehicle.start())

The above example prints: "Ford Engine Starting V8 [rodLength=6.0]"

Note for more complex configurations you can structure @ConfigurationProperties beans through inheritance.

For example creating a subclass of EngineConfig with @ConfigurationProperties('bar') will resolve all properties under the path my.engine.bar .

Includes / Excludes

For the cases where the configuration properties class is inheriting properties from a parent class, it may be desirable to exclude properties from the parent class. The includes and excludes members of the @ConfigurationProperties annotation allow for that functionality. The list applies to both local properties and inherited properties.

The names supplied to the includes/excludes list should be the "property" name. For example if a setter method is injected, the property name is the de-capitalized setter name ( setConnectionTimeoutconnectionTimeout ).

Property Type Conversion

When resolving properties Micronaut will use theConversionService bean to convert properties. You can register additional converters for types not supported by micronaut by defining beans that implement theTypeConverter interface.

Micronaut features some built-in conversions that are useful, which are detailed below.

Duration Conversion

Durations can be specified by appending the unit with a number. Supported units are s , ms , m etc. The following table summarizes examples:

Table 1. Duration Conversion
Configuration Value Resulting Value

10ms

Duration of 10 milliseconds

10m

Duration of 10 minutes

10s

Duration of 10 seconds

10d

Duration of 10 days

10h

Duration of 10 hours

10ns

Duration of 10 nanoseconds

PT15M

Duration of 15 minutes using ISO-8601 format

For example to configure the default HTTP client read timeout:

Using Duration Values

micronaut:
    http:
        client:
            read-timeout: 15s

List / Array Conversion

Lists and arrays can be specified in Java properties files as comma-separated values or in YAML using native YAML lists. The generic types are used to convert the values. For example in YAML:

Specifying lists or arrays in YAML

my:
    app:
        integers:
            - 1
            - 2
        urls:
            - http://foo.com
            - http://bar.com

Or in Java properties file format:

Specifying lists or arrays in Java properties comma-separated

my.app.integers=1,2
my.app.urls=http://foo.com,http://bar.com

Alternatively you can use an index:

Specifying lists or arrays in Java properties using index

my.app.integers[0]=1
my.app.integers[1]=2

For the above example configurations you can define properties to bind to with the target type supplied via generics:

List<Integer> integers;
List<URL> urls;

Readable Bytes

You can annotate any setter parameter with@ReadableBytes to allow the value to be set using a shorthand syntax for specifying bytes, kilobytes etc. For example the following is taken from HttpClientConfiguration :

Using @ReadableBytes
public void setMaxContentLength(@ReadableBytes int maxContentLength) {
    this.maxContentLength = maxContentLength;
}

With the above in place you can set micronaut.http.client.max-content-length using the following values:

Table 2. @ReadableBytes Conversion
Configuration Value Resulting Value

10mb

10 megabytes

10kb

10 kilobytes

10gb

10 gigabytes

1024

A raw byte length

Formatting Dates

The@Format annotation can be used on any setter to allow the date format to be specified when binding java.time date objects.

Using @Format for Dates
public void setMyDate(@Format("yyyy-MM-dd") LocalDate date) {
    this.myDate = date;
}

Configuration Builder

Many existing frameworks and tools already use builder-style classes to construct configuration.

To support the ability for a builder style class to be populated with configuration values, the @ConfigurationBuilder annotation can be used.ConfigurationBuilder can be added to a field or method in a class annotated with @ConfigurationProperties .

Since there is no consistent way to define builders in the Java world, one or more method prefixes can be specified in the annotation to support builder methods like withXxx or setXxx . If the builder methods have no prefix, assign an empty string to the parameter.

A configuration prefix can also be specified to tell Micronaut where to look for configuration values. By default, the builder methods will use the configuration prefix defined at the class level @ConfigurationProperties annotation.

For example:

import io.micronaut.context.annotation.ConfigurationBuilder;
import io.micronaut.context.annotation.ConfigurationProperties;

@ConfigurationProperties("my.engine") <b>(1)</b>
class EngineConfig {

    @ConfigurationBuilder(prefixes = "with") <b>(2)</b>
    EngineImpl.Builder builder = EngineImpl.builder();

    @ConfigurationBuilder(prefixes = "with", configurationPrefix = "crank-shaft") <b>(3)</b>
    CrankShaft.Builder crankShaft = CrankShaft.builder();

    private SparkPlug.Builder sparkPlug = SparkPlug.builder();

    SparkPlug.Builder getSparkPlug() { return this.sparkPlug; }

    @ConfigurationBuilder(prefixes = "with", configurationPrefix = "spark-plug") <b>(4)</b>
    void setSparkPlug(SparkPlug.Builder sparkPlug) {
        this.sparkPlug = sparkPlug;
    }
}
import io.micronaut.context.annotation.ConfigurationBuilder
import io.micronaut.context.annotation.ConfigurationProperties

@ConfigurationProperties('my.engine') <b>(1)</b>
class EngineConfig {

    @ConfigurationBuilder(prefixes = "with") <b>(2)</b>
    EngineImpl.Builder builder = EngineImpl.builder()

    @ConfigurationBuilder(prefixes = "with", configurationPrefix = "crank-shaft") <b>(3)</b>
    CrankShaft.Builder crankShaft = CrankShaft.builder()

    SparkPlug.Builder sparkPlug = SparkPlug.builder()

    @ConfigurationBuilder(prefixes = "with", configurationPrefix = "spark-plug") <b>(4)</b>
    void setSparkPlug(SparkPlug.Builder sparkPlug) {
        this.sparkPlug = sparkPlug
    }
}
import io.micronaut.context.annotation.ConfigurationBuilder
import io.micronaut.context.annotation.ConfigurationProperties

@ConfigurationProperties("my.engine") <b>(1)</b>
internal class EngineConfig {
    @ConfigurationBuilder(prefixes = ["with"])  <b>(2)</b>
    val builder = EngineImpl.builder()

    @ConfigurationBuilder(prefixes = ["with"], configurationPrefix = "crank-shaft") <b>(3)</b>
    val crankShaft = CrankShaft.builder()

    @set:ConfigurationBuilder(prefixes = ["with"], configurationPrefix = "spark-plug") <b>(4)</b>
    var sparkPlug = SparkPlug.builder()
}
1 The @ConfigurationProperties annotation takes the configuration prefix
2 The first builder can be configured without the class configuration prefix; it will inherit from the above.
3 The second builder can be configured with the class configuration prefix + the configurationPrefix value.
4 The third builder demonstrates that the annotation can be applied to a method as well as a property.
By default, only builder methods that take a single argument are supported. To support methods with no arguments, set the allowZeroArgs parameter of the annotation to true.

Just like in the previous example, we can construct an EngineImpl . Since we are using a builder, a factory class can be used to build the engine from the builder.

import io.micronaut.context.annotation.Factory;

import javax.inject.Singleton;

@Factory
class EngineFactory {

    @Singleton
    EngineImpl buildEngine(EngineConfig engineConfig) {
        return engineConfig.builder.build(engineConfig.crankShaft, engineConfig.getSparkPlug());
    }
}
import io.micronaut.context.annotation.Factory

import javax.inject.Singleton

@Factory
class EngineFactory {

    @Singleton
    EngineImpl buildEngine(EngineConfig engineConfig) {
        engineConfig.builder.build(engineConfig.crankShaft, engineConfig.sparkPlug)
    }
}
import io.micronaut.context.annotation.Factory
import javax.inject.Singleton

@Factory
internal class EngineFactory {

    @Singleton
    fun buildEngine(engineConfig: EngineConfig): EngineImpl {
        return engineConfig.builder.build(engineConfig.crankShaft, engineConfig.sparkPlug)
    }
}

The engine that was returned can then be injected anywhere an engine is depended on.

Configuration values can be supplied from one of thePropertySource instances. For example:

HashMap<String, Object> properties = new HashMap<>();
        properties.put("my.engine.cylinders"             ,"4");
        properties.put("my.engine.manufacturer"          , "Subaru");
        properties.put("my.engine.crank-shaft.rod-length", 4);
        properties.put("my.engine.spark-plug.name"       , "6619 LFR6AIX");
        properties.put("my.engine.spark-plug.type"       , "Iridium");
        properties.put("my.engine.spark-plug.companyName", "NGK");
        ApplicationContext applicationContext = ApplicationContext.run(properties, "test");

        Vehicle vehicle = applicationContext.getBean(Vehicle.class);
        System.out.println(vehicle.start());
ApplicationContext applicationContext = ApplicationContext.run(
                ['my.engine.cylinders'             : '4',
                 'my.engine.manufacturer'          : 'Subaru',
                 'my.engine.crank-shaft.rod-length': 4,
                 'my.engine.spark-plug.name'       : '6619 LFR6AIX',
                 'my.engine.spark-plug.type'       : 'Iridium',
                 'my.engine.spark-plug.companyName': 'NGK'
                ],
                "test"
        )

        Vehicle vehicle = applicationContext
                .getBean(Vehicle)
        println(vehicle.start())
val applicationContext = ApplicationContext.run(
                mapOf(
                        "my.engine.cylinders" to "4",
                        "my.engine.manufacturer" to "Subaru",
                        "my.engine.crank-shaft.rod-length" to 4,
                        "my.engine.spark-plug.name" to "6619 LFR6AIX",
                        "my.engine.spark-plug.type" to "Iridium",
                        "my.engine.spark-plug.company" to "NGK"
                ),
                "test"
        )

        val vehicle = applicationContext.getBean(Vehicle::class.java)
        println(vehicle.start())

The above example prints: "Subaru Engine Starting V4 [rodLength=4.0, sparkPlug=Iridium(NGK 6619 LFR6AIX)]"

MapFormat

For some use cases it may be desirable to accept a map of arbitrary configuration properties that can be supplied to a bean, especially if the bean represents a third-party API where not all of the possible configuration properties are known by the developer. For example, a datasource may accept a map of configuration properties specific to a particular database driver, allowing the user to specify any desired options in the map without coding every single property explicitly.

For this purpose, theMapFormat annotation allows you to bind a map to a single configuration property, and specify whether to accept a flat map of keys to values, or a nested map (where the values may be additional maps].

@MapFormat Example

import io.micronaut.context.annotation.ConfigurationProperties;
import javax.validation.constraints.Min;
import java.util.Map;
import io.micronaut.core.convert.format.MapFormat;

@ConfigurationProperties("my.engine")
public class EngineConfig {
    public int getCylinders() {
        return cylinders;
    }

    public void setCylinders(int cylinders) {
        this.cylinders = cylinders;
    }

    public Map<Integer, String> getSensors() {
        return sensors;
    }

    public void setSensors(Map<Integer, String> sensors) {
        this.sensors = sensors;
    }

    @Min(1L)
    private int cylinders;
    @MapFormat(transformation = MapFormat.MapTransformation.FLAT) <b>(1)</b>
    private Map<Integer, String> sensors;
}

@MapFormat Example

import io.micronaut.context.annotation.ConfigurationProperties
import javax.validation.constraints.Min
import io.micronaut.core.convert.format.MapFormat

@ConfigurationProperties('my.engine')
class EngineConfig {

    @Min(1L)
    int cylinders

    @MapFormat(transformation = MapFormat.MapTransformation.FLAT) <b>(1)</b>
    Map<Integer, String> sensors

}

@MapFormat Example

import io.micronaut.context.annotation.ConfigurationProperties
import javax.validation.constraints.Min
import io.micronaut.core.convert.format.MapFormat

@ConfigurationProperties("my.engine")
class EngineConfig {

    @Min(1L)
    var cylinders: Int = 0
    @MapFormat(transformation = MapFormat.MapTransformation.FLAT) <b>(1)</b>
    var sensors: Map<Int, String>? = null
}
1 Note the transformation argument to the annotation; possible values are MapTransformation.FLAT (for flat maps) and MapTransformation.NESTED (for nested maps)

EngineImpl

@Singleton
public class EngineImpl implements Engine {
    @Override
    public Map getSensors() {
        return config.getSensors();
    }

    public String start() {
        return "Engine Starting V" + getConfig().getCylinders() + " [sensors=" + getSensors().size() + "]";
    }

    public EngineConfig getConfig() {
        return config;
    }

    public void setConfig(EngineConfig config) {
        this.config = config;
    }

    @Inject
    private EngineConfig config;
}

EngineImpl

@Singleton
class EngineImpl implements Engine {

    @Inject EngineConfig config

    @Override
    Map getSensors() {
        config.sensors
    }

    String start() {
        "Engine Starting V${config.cylinders} [sensors=${sensors.size()}]"
    }
}

EngineImpl

@Singleton
class EngineImpl : Engine {
    override val sensors: Map<*, *>?
        get() = config!!.sensors

    @Inject
    var config: EngineConfig? = null

    override fun start(): String {
        return "Engine Starting V${config!!.cylinders} [sensors=${sensors!!.size}]"
    }
}

Now a map of properties can be supplied to the my.engine.sensors configuration property.

Use Map Configuration

LinkedHashMap<String, Object> map = new LinkedHashMap(2);
        map.put("my.engine.cylinders", "8");
        LinkedHashMap<Integer, String> map1 = new LinkedHashMap(2);
        map1.put(0, "thermostat");
        map1.put(1, "fuel pressure");
        map.put("my.engine.sensors", map1);
        ApplicationContext applicationContext = ApplicationContext.run(map, "test");

        Vehicle vehicle = applicationContext.getBean(Vehicle.class);
        DefaultGroovyMethods.println(this, vehicle.start());

Use Map Configuration

ApplicationContext applicationContext = ApplicationContext.run(
                ['my.engine.cylinders': '8', 'my.engine.sensors': [0: 'thermostat', 1: 'fuel pressure']],
                "test"
        )

        Vehicle vehicle = applicationContext
                .getBean(Vehicle)
        println(vehicle.start())

Use Map Configuration

val subMap = mapOf(
                0 to "thermostat",
                1 to "fuel pressure"
        )
        val map = mapOf(
                "my.engine.cylinders" to "8",
                "my.engine.sensors" to subMap
        )
        val applicationContext = ApplicationContext.run(map, "test")

        val vehicle = applicationContext.getBean(Vehicle::class.java)
        DefaultGroovyMethods.println(this, vehicle.start())

The above example prints: "Engine Starting V8 [sensors=2]"

4.5 Custom Type Converters

Micronaut features a built in type conversion mechanism that is extensible. To add additional type converters you register beans of typeTypeConverter.

The following example shows how to use one of the built-in converters (Map to an Object) or create your own.

Consider the following ConfigurationProperties :

@ConfigurationProperties(MyConfigurationProperties.PREFIX)
public class MyConfigurationProperties {
    public LocalDate getUpdatedAt() {
        return this.updatedAt;
    }

    public static final String PREFIX = "myapp";
    protected LocalDate updatedAt;
}
@ConfigurationProperties(MyConfigurationProperties.PREFIX)
class MyConfigurationProperties {
    public static final String PREFIX = "myapp"
    protected LocalDate updatedAt

    LocalDate getUpdatedAt() {
        return this.updatedAt
    }
}
@ConfigurationProperties(MyConfigurationProperties.PREFIX)
class MyConfigurationProperties {
    var updatedAt: LocalDate? = null
        protected set

    companion object {
        const val PREFIX = "myapp"
    }
}

The type MyConfigurationProperties features a property called updatedAt which is of type LocalDate .

Now let’s say you want to allow binding to this property from a map via configuration:

private static ApplicationContext ctx;

    @BeforeClass
    public static void setupCtx() {
        ctx = ApplicationContext.run(
                new LinkedHashMap<String, Object>() {{
                    put("myapp.updatedAt", <b>(1)</b>
                            new LinkedHashMap<String, Integer>() {{
                                put("day", 28);
                                put("month", 10);
                                put("year", 1982);
                            }}
                    );
                }}
        );
    }

    @AfterClass
    public static void teardownCtx() {
        if(ctx != null) {
            ctx.stop();
        }
    }
@AutoCleanup
    @Shared
    ApplicationContext ctx = ApplicationContext.run(
            "myapp.updatedAt": [day: 28, month: 10, year: 1982]  <b>(1)</b>
    )
lateinit var ctx: ApplicationContext

    @BeforeEach
    fun setup() {
        ctx = ApplicationContext.run(
                mapOf(
                        "myapp.updatedAt" to mapOf( <b>(1)</b>
                                "day" to 28,
                                "month" to 10,
                                "year" to 1982
                        )
                )
        )
    }

    @AfterEach
    fun teardown() {
        ctx?.close()
    }
1 Note how we match the myapp prefix and updatedAt property name in our MyConfigurationProperties class above

This won’t work by default, since there is no built in conversion from Map to LocalDate . To resolve this you can define a customTypeConverter:

import io.micronaut.core.convert.ConversionContext;
import io.micronaut.core.convert.ConversionService;
import io.micronaut.core.convert.TypeConverter;

import javax.inject.Singleton;
import java.time.DateTimeException;
import java.time.LocalDate;
import java.util.Map;
import java.util.Optional;

@Singleton
public class MapToLocalDateConverter implements TypeConverter<Map, LocalDate> { <b>(1)</b>
    @Override
    public Optional<LocalDate> convert(Map propertyMap, Class<LocalDate> targetType, ConversionContext context) {
        Optional<Integer> day = ConversionService.SHARED.convert(propertyMap.get("day"), Integer.class);
        Optional<Integer> month = ConversionService.SHARED.convert(propertyMap.get("month"), Integer.class);
        Optional<Integer> year = ConversionService.SHARED.convert(propertyMap.get("year"), Integer.class);
        if (day.isPresent() && month.isPresent() && year.isPresent()) {
            try {
                return Optional.of(LocalDate.of(year.get(), month.get(), day.get())); <b>(2)</b>
            } catch (DateTimeException e) {
                context.reject(propertyMap, e); <b>(3)</b>
                return Optional.empty();
            }

        }

        return Optional.empty();
    }
}
import io.micronaut.core.convert.ConversionContext
import io.micronaut.core.convert.ConversionService
import io.micronaut.core.convert.TypeConverter

import javax.inject.Singleton
import java.time.DateTimeException
import java.time.LocalDate

@Singleton
class MapToLocalDateConverter implements TypeConverter<Map, LocalDate> { <b>(1)</b>
    @Override
    Optional<LocalDate> convert(Map propertyMap, Class<LocalDate> targetType, ConversionContext context) {
        Optional<Integer> day = ConversionService.SHARED.convert(propertyMap.get("day"), Integer.class)
        Optional<Integer> month = ConversionService.SHARED.convert(propertyMap.get("month"), Integer.class)
        Optional<Integer> year = ConversionService.SHARED.convert(propertyMap.get("year"), Integer.class)
        if (day.isPresent() && month.isPresent() && year.isPresent()) {
            try {
                return Optional.of(LocalDate.of(year.get(), month.get(), day.get())) <b>(2)</b>
            } catch (DateTimeException e) {
                context.reject(propertyMap, e) <b>(3)</b>
                return Optional.empty()
            }
        }
        return Optional.empty()
    }
}
import io.micronaut.core.convert.ConversionContext
import io.micronaut.core.convert.ConversionService
import io.micronaut.core.convert.TypeConverter

import javax.inject.Singleton
import java.time.DateTimeException
import java.time.LocalDate
import java.util.Optional

@Singleton
class MapToLocalDateConverter : TypeConverter<Map<*, *>, LocalDate> { <b>(1)</b>
    override fun convert(propertyMap: Map<*, *>, targetType: Class<LocalDate>, context: ConversionContext): Optional<LocalDate> {
        val day = ConversionService.SHARED.convert(propertyMap["day"], Int::class.java)
        val month = ConversionService.SHARED.convert(propertyMap["month"], Int::class.java)
        val year = ConversionService.SHARED.convert(propertyMap["year"], Int::class.java)
        if (day.isPresent && month.isPresent && year.isPresent) {
            try {
                return Optional.of(LocalDate.of(year.get(), month.get(), day.get())) <b>(2)</b>
            } catch (e: DateTimeException) {
                context.reject(propertyMap, e) <b>(3)</b>
                return Optional.empty()
            }

        }

        return Optional.empty()
    }
}
1 The class implementsTypeConverter which takes two generic arguments. The type you are converting from and the type you are converting to
2 The implementation delegate to the default shared conversion service to convert the parts of the map that make the day, month and year into a LocalDate
3 If an exception occurs you can call reject(..) which propagates additional information to the container if something goes wrong during binding

4.6 Using @EachProperty to Drive Configuration

The @ConfigurationProperties annotation is great for a single configuration class, but sometimes you want multiple instances each with their own distinct configuration. That is whereEachProperty comes in.

The@EachProperty annotation will create a ConfigurationProperties bean for each sub-property within the given property. As an example consider the following class:

Using @EachProperty

import io.micronaut.context.annotation.Parameter;
import io.micronaut.context.annotation.EachProperty;

@EachProperty("test.datasource")  <b>(1)</b>
public class DataSourceConfiguration {

    private final String name;
    private URI url = new URI("localhost");

    public DataSourceConfiguration(@Parameter String name) <b>(2)</b>
            throws URISyntaxException {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public URI getUrl() { <b>(3)</b>
        return url;
    }

    public void setUrl(URI url) {
        this.url = url;
    }
}

Using @EachProperty

@EachProperty("test.datasource")
<b>(1)</b>
class DataSourceConfiguration {

    final String name
    URI url = new URI("localhost")

    DataSourceConfiguration(@Parameter String name) <b>(2)</b>
            throws URISyntaxException {
        this.name = name
    }
}

Using @EachProperty

@EachProperty("test.datasource")  <b>(1)</b>
class DataSourceConfiguration <b>(2)</b>
@Throws(URISyntaxException::class)
constructor(@param:Parameter val name: String) {
    <b>(3)</b>
    var url = URI("localhost")
}
1 The @EachProperty annotation defines the property name that should be handled.
2 The @Parameter annotation can be used to inject the name of the sub-property that defines the name of the bean (which is also the bean qualifier)
3 Each property of the bean is bound to configuration.

The above DataSourceConfiguration defines a url property to configure one or many hypothetical data sources of some sort. The URLs themselves can be configured using any of thePropertySource instances evaluated to Micronaut:

Providing Configuration to @EachProperty

ApplicationContext applicationContext = ApplicationContext.run(PropertySource.of(
                "test",
                CollectionUtils.mapOf(
                        "test.datasource.one.url", "jdbc:mysql://localhost/one",
                        "test.datasource.two.url", "jdbc:mysql://localhost/two")

        ));

Providing Configuration to @EachProperty

ApplicationContext applicationContext = ApplicationContext.run(PropertySource.of(
            "test",
            [
                    "test.datasource.one.url": "jdbc:mysql://localhost/one",
                    "test.datasource.two.url": "jdbc:mysql://localhost/two"
            ]
    ))

Providing Configuration to @EachProperty

val applicationContext = ApplicationContext.run(PropertySource.of(
                "test",
                mapOf(
                        "test.datasource.one.url" to "jdbc:mysql://localhost/one",
                        "test.datasource.two.url" to "jdbc:mysql://localhost/two"
                )
        ))

In the above example two data sources (called one and two ) are defined under the test.datasource prefix defined earlier in the @EachProperty annotation. Each of these configuration entries triggers the creation of a new DataSourceConfiguration bean such that the following test succeeds:

Evaluating Beans Built by @EachProperty

Collection<DataSourceConfiguration> beansOfType = applicationContext.getBeansOfType(DataSourceConfiguration.class);
        assertEquals(2, beansOfType.size()); <b>(1)</b>

        DataSourceConfiguration firstConfig = applicationContext.getBean(
                DataSourceConfiguration.class,
                Qualifiers.byName("one") <b>(2)</b>
        );

        assertEquals(
                new URI("jdbc:mysql://localhost/one"),
                firstConfig.getUrl()
        );

Evaluating Beans Built by @EachProperty

when:
        Collection<DataSourceConfiguration> beansOfType = applicationContext.getBeansOfType(DataSourceConfiguration.class)
        assertEquals(2, beansOfType.size()) <b>(1)</b>

        DataSourceConfiguration firstConfig = applicationContext.getBean(
                DataSourceConfiguration.class,
                Qualifiers.byName("one") <b>(2)</b>
        )

        then:
        new URI("jdbc:mysql://localhost/one") == firstConfig.getUrl()

Evaluating Beans Built by @EachProperty

val beansOfType = applicationContext.getBeansOfType(DataSourceConfiguration::class.java)
        assertEquals(2, beansOfType.size.toLong()) <b>(1)</b>

        val firstConfig = applicationContext.getBean(
                DataSourceConfiguration::class.java,
                Qualifiers.byName("one") <b>(2)</b>
        )

        assertEquals(
                URI("jdbc:mysql://localhost/one"),
                firstConfig.url
        )
1 All beans of type DataSourceConfiguration can be retrieved using getBeansOfType
2 Individual beans can be achieved by using the byName qualifier.

List Based Binding

The default behavior of@EachProperty is to bind from a map style of configuration where the key is the named qualifier of the bean and the value is the data to bind from. For cases where map style configuration doesn’t make sense, it is possible to inform Micronaut the class should be bound from a list. Simply set the list member on the annotation to true.

@EachProperty List Example

/*
 * Copyright 2017-2020 original authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.micronaut.docs.config.env;

import io.micronaut.context.annotation.EachProperty;
import io.micronaut.context.annotation.Parameter;
import io.micronaut.core.order.Ordered;

import java.time.Duration;

@EachProperty(value = "ratelimits", list = true) <b>(1)</b>
public class RateLimitsConfiguration implements Ordered { <b>(2)</b>

    private final Integer index;
    private Duration period;
    private Integer limit;

    RateLimitsConfiguration(@Parameter Integer index) { <b>(3)</b>
        this.index = index;
    }

    @Override
    public int getOrder() {
        return index;
    }

    public Duration getPeriod() {
        return period;
    }

    public void setPeriod(Duration period) {
        this.period = period;
    }

    public Integer getLimit() {
        return limit;
    }

    public void setLimit(Integer limit) {
        this.limit = limit;
    }

}

@EachProperty List Example

/*
 * Copyright 2017-2020 original authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.micronaut.docs.config.env

import io.micronaut.context.annotation.EachProperty
import io.micronaut.context.annotation.Parameter
import io.micronaut.core.order.Ordered

import java.time.Duration

@EachProperty(value = "ratelimits", list = true) <b>(1)</b>
class RateLimitsConfiguration implements Ordered { <b>(2)</b>

    private final Integer index
    Duration period
    Integer limit

    RateLimitsConfiguration(@Parameter Integer index) { <b>(3)</b>
        this.index = index
    }

    @Override
    int getOrder() {
        index
    }
}

@EachProperty List Example

/*
 * Copyright 2017-2020 original authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.micronaut.docs.config.env

import io.micronaut.context.annotation.EachProperty
import io.micronaut.context.annotation.Parameter
import io.micronaut.core.order.Ordered
import java.time.Duration

@EachProperty(value = "ratelimits", list = true) <b>(1)</b>
class RateLimitsConfiguration
    constructor(@param:Parameter private val index: Int) <b>(3)</b>
    : Ordered { <b>(2)</b>

    var period: Duration? = null
    var limit: Int? = null

    override fun getOrder(): Int {
        return index
    }

}
1 The list member of the annotation is set to true
2 Implement Ordered if order matters when retrieving the beans
3 The index is injected into the constructor

4.7 Using @EachBean to Drive Configuration

The@EachProperty is a great way to drive dynamic configuration, but typically you want to inject that configuration into another bean that depends on it. Injecting a single instance with a hard coded qualifier is not a great solution, hence @EachProperty is typically used in combination with@EachBean:

Using @EachBean

@Factory <b>(1)</b>
public class DataSourceFactory {

    @EachBean(DataSourceConfiguration.class) <b>(2)</b>
    DataSource dataSource(DataSourceConfiguration configuration) { <b>(3)</b>
        URI url = configuration.getUrl();
        return new DataSource(url);
    }

Using @EachBean

@Factory <b>(1)</b>
class DataSourceFactory {

    @EachBean(DataSourceConfiguration.class) <b>(2)</b>
    DataSource dataSource(DataSourceConfiguration configuration) { <b>(3)</b>
        URI url = configuration.getUrl()
        return new DataSource(url)
    }

Using @EachBean

@Factory <b>(1)</b>
class DataSourceFactory {

    @EachBean(DataSourceConfiguration::class) <b>(2)</b>
    internal fun dataSource(configuration: DataSourceConfiguration): DataSource { <b>(3)</b>
        val url = configuration.url
        return DataSource(url)
    }
1 The above example defines a beanFactory that will create instances of javax.sql.DataSource .
2 The@EachBean annotation is used to indicate that a new DataSource bean should be created for each DataSourceConfiguration defined in the previous section.
3 The DataSourceConfiguration instance is injected as a method argument and used to drive the configuration of each javax.sql.DataSource

Note that @EachBean requires that the parent bean has a @Named qualifier, since the qualifier is inherited by each bean created by @EachBean .

In other words, to retrieve the DataSource created by test.datasource.one you can do:

Using a Qualifier

Collection<DataSource> beansOfType = applicationContext.getBeansOfType(DataSource.class);
        assertEquals(2, beansOfType.size()); <b>(1)</b>

        DataSource firstConfig = applicationContext.getBean(
                DataSource.class,
                Qualifiers.byName("one") <b>(2)</b>
        );

Using a Qualifier

when:
        Collection<DataSource> beansOfType = applicationContext.getBeansOfType(DataSource.class)
        assertEquals(2, beansOfType.size()) <b>(1)</b>

        DataSource firstConfig = applicationContext.getBean(
                DataSource.class,
                Qualifiers.byName("one") <b>(2)</b>
        )

Using a Qualifier

val beansOfType = applicationContext.getBeansOfType(DataSource::class.java)
        assertEquals(2, beansOfType.size.toLong()) <b>(1)</b>

        val firstConfig = applicationContext.getBean(
                DataSource::class.java,
                Qualifiers.byName("one") <b>(2)</b>
        )
1 We demonstrate here that there are indeed two data sources. How can we get one in particular?
2 By using Qualifiers.byName("one") , we can select which of the two beans we’d like to reference.

4.8 Immutable Configuration

Since 1.3, Micronaut supports the definition of immutable configuration.

There are two ways to define immutable configuration. The preferred way is to define an interface that is annotated with @ConfigurationProperties . For example:

@ConfigurationProperties Example

import io.micronaut.context.annotation.ConfigurationProperties;
import io.micronaut.core.bind.annotation.Bindable;
import javax.validation.constraints.*;
import java.util.Optional;

@ConfigurationProperties("my.engine") <b>(1)</b>
public interface EngineConfig {

    @Bindable(defaultValue = "Ford") <b>(2)</b>
    @NotBlank <b>(3)</b>
    String getManufacturer();

    @Min(1L)
    int getCylinders();

    @NotNull
    CrankShaft getCrankShaft(); <b>(4)</b>

    @ConfigurationProperties("crank-shaft")
    interface CrankShaft { <b>(5)</b>
        Optional<Double> getRodLength(); <b>(6)</b>
    }
}

@ConfigurationProperties Example

import io.micronaut.context.annotation.ConfigurationProperties
import io.micronaut.core.bind.annotation.Bindable
import javax.validation.constraints.*


@ConfigurationProperties("my.engine") <b>(1)</b>
interface EngineConfig {

    @Bindable(defaultValue = "Ford") <b>(2)</b>
    @NotBlank <b>(3)</b>
    String getManufacturer()

    @Min(1L)
    int getCylinders()

    @NotNull
    CrankShaft getCrankShaft() <b>(4)</b>

    @ConfigurationProperties("crank-shaft")
    static interface CrankShaft { <b>(5)</b>
        Optional<Double> getRodLength() <b>(6)</b>
    }
}

@ConfigurationProperties Example

import io.micronaut.context.annotation.ConfigurationProperties
import io.micronaut.core.bind.annotation.Bindable
import javax.validation.constraints.*
import java.util.Optional


@ConfigurationProperties("my.engine") <b>(1)</b>
interface EngineConfig {

    @get:Bindable(defaultValue = "Ford") <b>(2)</b>
    @get:NotBlank <b>(3)</b>
    val manufacturer: String

    @get:Min(1L)
    val cylinders: Int

    @get:NotNull
    val crankShaft: CrankShaft <b>(4)</b>

    @ConfigurationProperties("crank-shaft")
    interface CrankShaft { <b>(5)</b>
        val rodLength: Double? <b>(6)</b>
    }
}
1 The @ConfigurationProperties annotation takes the configuration prefix and is declared on an interface
2 You can use@Bindable to set a default value if you want
3 Validation annotations can be used too
4 You can also specify references to other @ConfigurationProperties beans.
5 You can nest immutable configuration
6 Optional configuration can be indicated by returning an Optional or specifying @Nullable

In this case what Micronaut does is provide a compilation time implementation that delegates all getters to call the getProperty(..) method of theEnvironment interface.

This has the advantage that if the application’s configuration is(for example by invoking the /refresh endpoint) then the injected interface will automatically see the new values.

If you try to specify any other abstract method other than a getter a compilation error will occur (default methods are supported).

An alternative way to implement immutable configuration is to define a class and use the@ConfigurationInject annotation on a constructor of a @ConfigurationProperties or@EachProperty bean.

An example of an immutable configuration class can be seen below:

@ConfigurationProperties Example

import io.micronaut.context.annotation.*;
import io.micronaut.core.bind.annotation.Bindable;
import edu.umd.cs.findbugs.annotations.Nullable;
import javax.validation.constraints.*;
import java.util.Optional;

@ConfigurationProperties("my.engine") <b>(1)</b>
public class EngineConfig {

    private final String manufacturer;
    private final int cylinders;
    private final CrankShaft crankShaft;

    @ConfigurationInject <b>(2)</b>
    public EngineConfig(
            @Bindable(defaultValue = "Ford") @NotBlank String manufacturer, <b>(3)</b>
            @Min(1L) int cylinders, <b>(4)</b>
            @NotNull CrankShaft crankShaft) {
        this.manufacturer = manufacturer;
        this.cylinders = cylinders;
        this.crankShaft = crankShaft;
    }

    public String getManufacturer() {
        return manufacturer;
    }

    public int getCylinders() {
        return cylinders;
    }

    public CrankShaft getCrankShaft() {
        return crankShaft;
    }

    @ConfigurationProperties("crank-shaft")
    public static class CrankShaft { <b>(5)</b>
        private final Double rodLength; <b>(6)</b>

        @ConfigurationInject
        public CrankShaft(@Nullable Double rodLength) {
            this.rodLength = rodLength;
        }

        public Optional<Double> getRodLength() {
            return Optional.ofNullable(rodLength);
        }
    }
}

@ConfigurationProperties Example

import io.micronaut.context.annotation.*
import io.micronaut.core.bind.annotation.Bindable
import javax.annotation.Nullable
import javax.validation.constraints.Min
import javax.validation.constraints.NotBlank
import javax.validation.constraints.NotNull


@ConfigurationProperties("my.engine") <b>(1)</b>
class EngineConfig {

    private final String manufacturer
    private final int cylinders
    private final CrankShaft crankShaft

    @ConfigurationInject <b>(2)</b>
    EngineConfig(
                  @Bindable(defaultValue = "Ford") @NotBlank String manufacturer, <b>(3)</b>
                  @Min(1L) int cylinders, <b>(4)</b>
                  @NotNull CrankShaft crankShaft) {
        this.manufacturer = manufacturer
        this.cylinders = cylinders
        this.crankShaft = crankShaft
    }

    String getManufacturer() {
        return manufacturer
    }

    int getCylinders() {
        return cylinders
    }

    CrankShaft getCrankShaft() {
        return crankShaft
    }

    @ConfigurationProperties("crank-shaft")
    static class CrankShaft { <b>(5)</b>
        private final Double rodLength <b>(6)</b>

        @ConfigurationInject
        CrankShaft(@Nullable Double rodLength) {
            this.rodLength = rodLength
        }

        Optional<Double> getRodLength() {
            return Optional.ofNullable(rodLength)
        }
    }
}

@ConfigurationProperties Example

import io.micronaut.context.annotation.*
import io.micronaut.core.bind.annotation.Bindable
import javax.validation.constraints.*
import java.util.Optional


@ConfigurationProperties("my.engine") <b>(1)</b>
data class EngineConfig @ConfigurationInject <b>(2)</b>
    constructor(
        @Bindable(defaultValue = "Ford") @NotBlank <b>(3)</b>
        val manufacturer: String,
        @Min(1L) <b>(4)</b>
        val cylinders: Int,
        @NotNull val crankShaft: CrankShaft) {

    @ConfigurationProperties("crank-shaft")
    data class CrankShaft @ConfigurationInject
    constructor(<b>(5)</b>
            private val rodLength: Double? <b>(6)</b>
    ) {

        fun getRodLength(): Optional<Double> {
            return Optional.ofNullable(rodLength)
        }
    }
}
1 The @ConfigurationProperties annotation takes the configuration prefix
2 The@ConfigurationInject annotation is defined on the constructor
3 You can use@Bindable to set a default value if you want
4 Validation annotations can be used too
5 You can nest immutable configuration
6 Optional configuration can be indicated with @Nullable

The@ConfigurationInject annotation provides a hint to Micronaut to prioritize binding values from configuration instead of injecting beans.

Using this approach if you want the configuration to be refreshable then you must add the@Refreshable annotation to the class as well. This allows the bean to be re-created in the case of aruntime configuration refresh event.

There are a few exceptions to this rule. Micronaut will not perform configuration binding for a parameter if one of these conditions is met:

  • The parameter is annotated with @Value (explicit binding)

  • The parameter is annotated with @Property (explicit binding)

  • The parameter is annotated with @Parameter (parameterized bean handling)

  • The type of the parameter is annotated with a bean scope (such as @Singleton )

Once you have prepared a type safe configuration it can simply be injected into your objects like any other bean:

1 Inject the EngineConfig bean
2 Use the configuration properties

Configuration values can then be supplied when running the application. For example:

Supply Configuration

ApplicationContext applicationContext = ApplicationContext.run(CollectionUtils.mapOf(
                "my.engine.cylinders", "8",
                "my.engine.crank-shaft.rod-length", "7.0"
        ));

        Vehicle vehicle = applicationContext.getBean(Vehicle.class);
        System.out.println(vehicle.start());

Supply Configuration

ApplicationContext applicationContext = ApplicationContext.run(
                "my.engine.cylinders": "8",
                "my.engine.crank-shaft.rod-length": "7.0"
        )

        Vehicle vehicle = applicationContext.getBean(Vehicle.class)
        System.out.println(vehicle.start())

Supply Configuration

val map = mapOf(
                "my.engine.cylinders" to "8",
                "my.engine.crank-shaft.rod-length" to "7.0"
        )
        val applicationContext = ApplicationContext.run(map)

        val vehicle = applicationContext.getBean(Vehicle::class.java)

The above example prints: "Ford Engine Starting V8 [rodLength=7B.0]"

4.9 JMX Support

Micronaut provides basic support for JMX.

For more information, see the documentation for the micronaut-jmx project.

5 Aspect Oriented Programming

Aspect-Oriented Programming (AOP) historically has had many incarnations and some very complicated implementations. Generally AOP can be thought of as a way to define cross cutting concerns (logging, transactions, tracing etc.) separate from application code in the form of aspects that define advice.

There are typically two forms of advice:

  • Around Advice - decorates a method or class

  • Introduction Advice - introduces new behaviour to a class.

In modern Java applications declaring advice typically takes the form of an annotation. The most well-known annotation advice in the Java world is probably @Transactional which is used to demarcate transaction boundaries in Spring and Grails applications.

The disadvantage of traditional approaches to AOP is the heavy reliance on runtime proxy creation and reflection, which slows application performance, makes debugging harder and increases memory consumption.

Micronaut tries to address these concerns by providing a simple compile time AOP API that does not use reflection.

5.1 Around Advice

The most common type of advice you may want to apply is "Around" advice, which essentially allows you to decorate a methods behaviour.

Writing Around Advice

The first step to defining Around advice is to implement aMethodInterceptor. For example the following interceptor disallows parameters with null values:

MethodInterceptor Example

import io.micronaut.aop.*;
import io.micronaut.core.type.MutableArgumentValue;

import javax.inject.Singleton;
import java.util.*;

@Singleton
public class NotNullInterceptor implements MethodInterceptor<Object, Object> { <b>(1)</b>
    @Override
    public Object intercept(MethodInvocationContext<Object, Object> context) {
        Optional<Map.Entry<String, MutableArgumentValue<?>>> nullParam = context.getParameters()
            .entrySet()
            .stream()
            .filter(entry -> {
                MutableArgumentValue<?> argumentValue = entry.getValue();
                return Objects.isNull(argumentValue.getValue());
            })
            .findFirst(); <b>(2)</b>
        if (nullParam.isPresent()) {
            throw new IllegalArgumentException("Null parameter [" + nullParam.get().getKey() + "] not allowed"); <b>(3)</b>
        } else {
            return context.proceed(); <b>(4)</b>
        }
    }
}

MethodInterceptor Example

import io.micronaut.aop.MethodInterceptor
import io.micronaut.aop.MethodInvocationContext
import io.micronaut.core.type.MutableArgumentValue

import javax.inject.Singleton

@Singleton
class NotNullInterceptor implements MethodInterceptor<Object, Object> { <b>(1)</b>
    @Override
    Object intercept(MethodInvocationContext<Object, Object> context) {
        Optional<Map.Entry<String, MutableArgumentValue<?>>> nullParam = context.getParameters()
            .entrySet()
            .stream()
            .filter({entry ->
                MutableArgumentValue<?> argumentValue = entry.getValue()
                return Objects.isNull(argumentValue.getValue())
            })
            .findFirst() <b>(2)</b>
        if (nullParam.isPresent()) {
            throw new IllegalArgumentException("Null parameter [" + nullParam.get().getKey() + "] not allowed") <b>(3)</b>
        } else {
            return context.proceed() <b>(4)</b>
        }
    }
}

MethodInterceptor Example

import io.micronaut.aop.MethodInterceptor
import io.micronaut.aop.MethodInvocationContext
import io.micronaut.core.type.MutableArgumentValue

import javax.inject.Singleton
import java.util.Objects
import java.util.Optional


@Singleton
class NotNullInterceptor : MethodInterceptor<Any, Any> { <b>(1)</b>
    override fun intercept(context: MethodInvocationContext<Any, Any>): Any {
        val nullParam = context.parameters
                .entries
                .stream()
                .filter { entry ->
                    val argumentValue = entry.value
                    Objects.isNull(argumentValue.value)
                }
                .findFirst() <b>(2)</b>
        return if (nullParam.isPresent) {
            throw IllegalArgumentException("Null parameter [" + nullParam.get().key + "] not allowed") <b>(3)</b>
        } else {
            context.proceed() <b>(4)</b>
        }
    }
}
1 An interceptor implements theMethodInterceptor interface
2 The passed MethodInvocationContext is used to find the first parameter that is null
3 If a null parameter is found an exception is thrown
4 Otherwiseproceed() is called to proceed with the method invocation.
Micronaut AOP interceptors use no reflection which improves performance and reducing stack trace sizes, thus improving debugging.

To put the new MethodInterceptor to work the next step is to define an annotation that will trigger the MethodInterceptor :

Around Advice Annotation Example

import io.micronaut.context.annotation.Type;
import io.micronaut.aop.Around;
import java.lang.annotation.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Documented
@Retention(RUNTIME) <b>(1)</b>
@Target({ElementType.TYPE, ElementType.METHOD}) <b>(2)</b>
@Around <b>(3)</b>
@Type(NotNullInterceptor.class) <b>(4)</b>
public @interface NotNull {
}

Around Advice Annotation Example

import io.micronaut.aop.Around
import io.micronaut.context.annotation.Type

import java.lang.annotation.Documented
import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.Target

import static java.lang.annotation.RetentionPolicy.RUNTIME

@Documented
@Retention(RUNTIME) <b>(1)</b>
@Target([ElementType.TYPE, ElementType.METHOD]) <b>(2)</b>
@Around <b>(3)</b>
@Type(NotNullInterceptor.class) <b>(4)</b>
@interface NotNull {
}

Around Advice Annotation Example

import io.micronaut.aop.Around
import io.micronaut.context.annotation.Type

import java.lang.annotation.Documented
import java.lang.annotation.Retention

import java.lang.annotation.RetentionPolicy.RUNTIME


@Documented
@Retention(RUNTIME) <b>(1)</b>
@Target(AnnotationTarget.CLASS, AnnotationTarget.FILE, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) <b>(2)</b>
@Around <b>(3)</b>
@Type(NotNullInterceptor::class) <b>(4)</b>
annotation class NotNull
1 The retention policy of the annotation should be RUNTIME
2 Generally you want to be able to apply advice at the class or method level so the target types are TYPE and METHOD
3 TheAround annotation is added to tell Micronaut that the annotation is Around advice
4 The@Type annotation is used to configure which type implements the advice (in this case the previously defined NotNullInterceptor )

With the interceptor and annotation implemented you can then simply apply the annotation to the target classes:

Around Advice Usage Example

@Singleton
public class NotNullExample {

    @NotNull
    void doWork(String taskName) {
        System.out.println("Doing job: " + taskName);
    }
}

Around Advice Usage Example

@Singleton
class NotNullExample {

    @NotNull
    void doWork(String taskName) {
        println("Doing job: " + taskName)
    }
}

Around Advice Usage Example

@Singleton
open class NotNullExample {

    @NotNull
    open fun doWork(taskName: String?) {
        println("Doing job: $taskName")
    }
}

Whenever the type NotNullExample is injected into any class, a compile time generated proxy will instead be injected that decorates the appropriate method calls with the @NotNull advice defined earlier. You can verify that the advice works by writing a test. The following test uses a JUnit ExpectedException rule to verify the appropriate exception is thrown when an argument is null :

Around Advice Test

@Rule
    public ExpectedException thrown = ExpectedException.none();

    @Test
    public void testNotNull() {
        try (ApplicationContext applicationContext = ApplicationContext.run()) {
            NotNullExample exampleBean = applicationContext.getBean(NotNullExample.class);

            thrown.expect(IllegalArgumentException.class);
            thrown.expectMessage("Null parameter [taskName] not allowed");

            exampleBean.doWork(null);
        }
    }

Around Advice Test

void "test not null"() {
        when:
        ApplicationContext applicationContext = ApplicationContext.run()
        NotNullExample exampleBean = applicationContext.getBean(NotNullExample.class)

        exampleBean.doWork(null)

        then:
        IllegalArgumentException ex = thrown()
        ex.message == 'Null parameter [taskName] not allowed'

        cleanup:
        applicationContext.close()
    }

Around Advice Test

@Test
    fun testNotNull() {
        val applicationContext = ApplicationContext.run()
        val exampleBean = applicationContext.getBean(NotNullExample::class.java)

        val exception = shouldThrow<IllegalArgumentException> {
            exampleBean.doWork(null)
        }
        exception.message shouldBe "Null parameter [taskName] not allowed"
        applicationContext.close()
    }
Since Micronaut injection is done at compile time, generally the advice should be packaged in a dependent JAR file that is on the classpath when the above test is compiled and should not be in the same codebase since you don’t want the test to be compiled before the advice itself is compiled.

Customizing Proxy Generation

The default behaviour of theAround annotation is to generate a proxy at compile time that is a subclass of the class being proxied. In other words, in the previous example a compile time subclass of the NotNullExample class will be produced where methods that are proxied are decorated with interceptor handling and the original behaviour is invoked via a call to super .

This behaviour is more efficient as only one instance of the bean is required, however depending on the use case you are trying to implement you may wish to alter this behaviour and the @Around annotation supports various attributes that allow you to alter this behaviour including:

  • proxyTarget (defaults to false ) - If set to true instead of a subclass that calls super , the proxy will delegate to the original bean instance

  • hotswap (defaults to false ) - Same as proxyTarget=true , but in addition the proxy will implement HotSwappableInterceptedProxy which wraps each method call in a ReentrantReadWriteLock and allows swapping the target instance at runtime.

  • lazy (defaults to false ) - By default Micronaut will eagerly intialize the proxy target when the proxy is created. If set to true the proxy target will instead be resolved lazily for each method call.

AOP Advice on @Factory Beans

The semantics of AOP advice when applied todiffers to regular beans, with the following rules applying:

  1. AOP advice applied at the class level of a@Factory bean will apply the advice to the factory itself and not to any beans defined with the@Bean annotation.

  2. AOP advice applied on a method annotated with a bean scope will apply the AOP advice to the bean that the factory produces.

Consider the following two examples:

AOP Advice at the type level of a @Factory
@Timed
@Factory
public class MyFactory {

    @Prototype
    public MyBean myBean() {
        return new MyBean();
    }
}
AOP Advice at the type level of a @Factory
@Timed
@Factory
class MyFactory {

    @Prototype
    MyBean myBean() {
        return new MyBean()
    }
}
AOP Advice at the type level of a @Factory
@Timed
@Factory
open class MyFactory {

    @Prototype
    open fun myBean(): MyBean {
        return MyBean()
    }
}

The above example will log the time it takes to create the MyBean bean.

Now consider this example:

AOP Advice at the method level of a @Factory
@Factory
public class MyFactory {

    @Prototype
    @Timed
    public MyBean myBean() {
        return new MyBean();
    }
}
AOP Advice at the method level of a @Factory
@Factory
class MyFactory {

    @Prototype
    @Timed
    MyBean myBean() {
        return new MyBean()
    }
}
AOP Advice at the method level of a @Factory
@Factory
open class MyFactory {

    @Prototype
    @Timed
    open fun myBean(): MyBean {
        return MyBean()
    }
}

The above example will log the time it takes to execute all of the public methods of the MyBean bean, but not the bean creation.

The rationale for this behaviour is that you may at times wish to apply advice to a factory and at other times apply advice to the bean produced by the factory.

Note that there is currently no way to apply advice at the method level to a@Factory bean and all advice for factories must be applied at the type level. You can control which methods have advice applied by defining methods as non-public (non-public methods do not have advice applied).

5.2 Introduction Advice

Introduction advice is distinct from Around advice in that it involves providing an implementation instead of decorating.

Examples of introduction advice include things like GORM or Spring Data that will both automatically implement persistence logic for you.

Micronaut’sClient annotation is another example of introduction advice where Micronaut will, at compile time, implement HTTP client interfaces for you.

The way you implement Introduction advice is very similar to how you implement Around advice.

You start off by defining an annotation that will power the introduction advice. As an example, say you want to implement advice that will return a stubbed value for every method in an interface (a common requirement in testing frameworks). Consider the following @Stub annotation:

Introduction Advice Annotation Example

import static java.lang.annotation.RetentionPolicy.RUNTIME;

import io.micronaut.aop.Introduction;
import io.micronaut.context.annotation.Bean;
import io.micronaut.context.annotation.Type;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Introduction <b>(1)</b>
@Type(StubIntroduction.class) <b>(2)</b>
@Bean <b>(3)</b>
@Documented
@Retention(RUNTIME)
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.METHOD})
public @interface Stub {
    String value() default "";
}

Introduction Advice Annotation Example

import io.micronaut.aop.Introduction
import io.micronaut.context.annotation.Bean
import io.micronaut.context.annotation.Type

import java.lang.annotation.Documented
import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.Target

import static java.lang.annotation.RetentionPolicy.RUNTIME

@Introduction <b>(1)</b>
@Type(StubIntroduction.class) <b>(2)</b>
@Bean <b>(3)</b>
@Documented
@Retention(RUNTIME)
@Target([ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.METHOD])
@interface Stub {
    String value() default ""
}

Introduction Advice Annotation Example

import io.micronaut.aop.Introduction
import io.micronaut.context.annotation.Bean
import io.micronaut.context.annotation.Type

import java.lang.annotation.Documented
import java.lang.annotation.Retention

import java.lang.annotation.RetentionPolicy.RUNTIME


@Introduction <b>(1)</b>
@Type(StubIntroduction::class) <b>(2)</b>
@Bean <b>(3)</b>
@Documented
@Retention(RUNTIME)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FILE, AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
annotation class Stub(val value: String = "")
1 The introduction advice is annotated withIntroduction
2 TheType annotation is used to refer to the implementor of the advice. In this case StubIntroduction
3 TheBean annotation is added so that all types annotated with @Stub become beans

The StubIntroduction class referred to in the previous example must then implement theMethodInterceptor interface, just like around advice.

The following is an example implementation:

StubIntroduction

import io.micronaut.aop.*;
import javax.inject.Singleton;

@Singleton
public class StubIntroduction implements MethodInterceptor<Object,Object> { <b>(1)</b>

    @Override
    public Object intercept(MethodInvocationContext<Object, Object> context) {
        return context.getValue( <b>(2)</b>
                Stub.class,
                context.getReturnType().getType()
        ).orElse(null); <b>(3)</b>
    }
}

StubIntroduction

import io.micronaut.aop.MethodInterceptor
import io.micronaut.aop.MethodInvocationContext

import javax.inject.Singleton

@Singleton
class StubIntroduction implements MethodInterceptor<Object,Object> { <b>(1)</b>

    @Override
    Object intercept(MethodInvocationContext<Object, Object> context) {
        return context.getValue( <b>(2)</b>
                Stub.class,
                context.getReturnType().getType()
        ).orElse(null) <b>(3)</b>
    }
}

StubIntroduction

import io.micronaut.aop.MethodInterceptor
import io.micronaut.aop.MethodInvocationContext

import javax.inject.Singleton


@Singleton
class StubIntroduction : MethodInterceptor<Any, Any> { <b>(1)</b>

    override fun intercept(context: MethodInvocationContext<Any, Any>): Any? {
        return context.getValue<Any>( <b>(2)</b>
                Stub::class.java,
                context.returnType.type
        ).orElse(null) <b>(3)</b>
    }
}
1 The class is annotated with @Singleton and implements theMethodInterceptor interface
2 The value of the @Stub annotation is read from the context and an attempt made to convert the value to the return type
3 Otherwise null is returned

To now use this introduction advice in an application you simply annotate your abstract classes or interfaces with @Stub :

StubExample

@Stub
public interface StubExample {

    @Stub("10")
    int getNumber();

    LocalDateTime getDate();
}

StubExample

@Stub
interface StubExample {

    @Stub("10")
    int getNumber()

    LocalDateTime getDate()
}

StubExample

@Stub
interface StubExample {

    @get:Stub("10")
    val number: Int

    val date: LocalDateTime?
}

All abstract methods will delegate to the StubIntroduction class to be implemented.

The following test demonstrates the behaviour or StubIntroduction :

Testing Introduction Advice

StubExample stubExample = applicationContext.getBean(StubExample.class);

            assertEquals(10, stubExample.getNumber());
            assertNull(stubExample.getDate());

Testing Introduction Advice

when:
        StubExample stubExample = applicationContext.getBean(StubExample.class)

        then:
        stubExample.getNumber() == 10
        stubExample.getDate() == null

Testing Introduction Advice

val stubExample = applicationContext.getBean(StubExample::class.java)

        stubExample.number.toLong().shouldBe(10)
        stubExample.date.shouldBe(null)

Note that if the introduction advice cannot implement the method the proceed method of the MethodInvocationContext should be called. This gives the opportunity for other introduction advice interceptors to implement the method, otherwise a UnsupportedOperationException will be thrown if no advice can implement the method.

In addition, if multiple introduction advice are present you may wish to override the getOrder() method ofMethodInterceptor to control the priority of advise.

The following sections cover core advice types that are built into Micronaut and provided by the framework.

5.3 Method Adapter Advice

There are sometimes cases where you want to introduce a new bean based on the presence of an annotation on a method. An example of this case is the@EventListener annotation which for each method annotated with@EventListener produces an implementation of ApplicationEventListener that invokes the annotated method.

For example the following snippet will run the logic contained within the method when theApplicationContext starts up:

import io.micronaut.context.event.StartupEvent;
import io.micronaut.runtime.event.annotation.EventListener;
...

@EventListener
void onStartup(StartupEvent event) {
    // startup logic here
}

The presence of the@EventListener annotation causes Micronaut to create a new class that implements the ApplicationEventListener and invokes the onStartup method defined in the bean above.

The actual implementation of the@EventListener is trivial, it simply uses the@Adapter annotation to specify which SAM (single abstract method) type it adapts:

import io.micronaut.aop.Adapter;
import io.micronaut.context.event.ApplicationEventListener;
import io.micronaut.core.annotation.Indexed;

import java.lang.annotation.*;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Documented
@Retention(RUNTIME)
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
@Adapter(ApplicationEventListener.class) <i data-value="1"></i><b>(1)</b>
@Indexed(ApplicationEventListener.class)
public @interface EventListener {
}
1 The@Adapter annotation is used to indicate which SAM type to adapt. In this case ApplicationEventListener .
Micronaut will also automatically align the generic types for the SAM interface if they are specified.

Using this mechanism you can define custom annotations that use the@Adapter annotation and a SAM interface to automatically implement beans for you at compile time.

5.4 Validation Advice

Validation advice is one of the most common advice types you are likely to want to incorporate into your application.

Validation advice is built on Bean Validation JSR 380 .

JSR 380 is a specification of the Java API for bean validation which ensures that the properties of a bean meet specific criteria, using javax.validation annotations such as @NotNull , @Min , and @Max .

Micronaut provides native support for the javax.validation annotations with the micronaut-validation dependency:

implementation("io.micronaut:micronaut-validation")
<dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-validation</artifactId>
</dependency>

Or full JSR 380 compliance with the micronaut-hibernate-validator dependency:

implementation("io.micronaut:micronaut-hibernate-validator")
<dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-hibernate-validator</artifactId>
</dependency>

See the section onfor more information on how to apply validation rules to your bean classes.

5.5 Cache Advice

Similar to Spring and Grails, Micronaut provides a set of caching annotations within theio.micronaut.cache package.

TheCacheManager interface allows different cache implementations to be plugged in as necessary.

TheSyncCache interface provides a synchronous API for caching, whilst theAsyncCache API allows non-blocking operation.

Cache Annotations

The following cache annotations are supported:

  • @Cacheable - Indicates a method is cacheable within the given cache name

  • @CachePut - Indicates that the return value of a method invocation should be cached. Unlike @Cacheable the original operation is never skipped.

  • @CacheInvalidate - Indicates the invocation of a method should cause the invalidation of one or many caches.

By using one of the annotations theCacheInterceptor is activated which in the case of @Cacheable will cache the return result of the method.

If the return type of the method is a non-blocking type (either CompletableFuture or an instance of Publisher the emitted result will be cached.

In addition if the underlying Cache implementation supports non-blocking cache operations then cache values will be read from the cache without blocking, resulting in the ability to implement completely non-blocking cache operations.

Configuring Caches

By default Caffeine is used for cache definitions which can be configured via application configuration. For example with application.yml :

Cache Configuration Example

micronaut:
    caches:
        my-cache:
            maximum-size: 20

The above example will configure a cache called "my-cache" with a maximum size of 20.

Naming Caches

Names of caches under micronaut.caches should be defined in kebab case (lowercase and hyphen seperated), if camel case is used the names are normalized to kebab case. So for example specifing myCache will become my-cache . The kebab case form should be used when referencing caches in the@Cacheable annotation.

To configure a weigher to be used with the maximumWeight configuration, create a bean that implements io.micronaut.caffeine.cache.Weigher . To associate a given weigher with only a specific cache, annotate the bean with @Named(<cache name>) . Weighers without a named qualifier will apply to all caches that don’t have a named weigher. If no beans are found, a default implementation will be used.

Unresolved directive in <stdin> - include::/home/travis/build/micronaut-projects/micronaut-core/build/generated/configurationProperties/io.micronaut.cache.DefaultCacheConfiguration.adoc[]

Dynamic Cache Creation

For use cases where caches cannot be configured ahead of time, aDynamicCacheManager bean can be registered. When a cache is attempted to be retrieved that was not predefined, the dynamic cache manager will be invoked to return a cache.

By default, if there is no other dynamic cache manager defined in the application, Micronaut registers an instance of DefaultDynamicCacheManager that will create Caffeine caches with default values.

Other Cache Implementations

Check the Micronaut Cache project for more information.

5.6 Retry Advice

In distributed systems and Microservice environments, failure is something you have to plan for and it is pretty common to want to attempt to retry an operation if it fails. If first you don’t succeed try again!

With this in mind Micronaut comes with aRetryable annotation out of the box that is integrated into the container.

Simple Retry

The simplest form of retry is just to add the @Retryable annotation to any type or method. The default behaviour of @Retryable is to retry 3 times with a delay of 1 second between each retry.

For example:

Simple Retry Example

@Retryable
    public List<Book> listBooks() {
        // ...

Simple Retry Example

@Retryable
    List<Book> listBooks() {
        // ...

Simple Retry Example

@Retryable
    open fun listBooks(): List<Book> {
        // ...

With the above example if the listBooks() method throws an exception it will be retried until the maximum number of attempts is reached.

The multiplier value of the @Retryable annotation can be used to configure a multiplier used to calculate the delay between retries, thus allowing exponential retry support.

Note also that the @Retryable annotation can be applied on interfaces and the behaviour will be inherited through annotation metadata. The implication of this is that @Retryable can be used in combination withsuch as the HTTPClient annotation.

To customize retry behaviour you can set the attempts and delay members, For example to configure 5 attempts with a 2 second delay:

Setting Retry Attempts

@Retryable( attempts = "5",
                delay = "2s" )
    public Book findBook(String title) {
        // ...

Setting Retry Attempts

@Retryable( attempts = "5",
                delay = "2s" )
    Book findBook(String title) {
        // ...

Setting Retry Attempts

@Retryable(attempts = "5", delay = "2s")
    open fun findBook(title: String): Book {
        // ...

Notice how both attempts and delay are defined as strings. This is to support configurability through annotation metadata. For example you can allow the retry policy to be configured using property placeholder resolution:

Setting Retry via Configuration

@Retryable( attempts = "${book.retry.attempts:3}",
                delay = "${book.retry.delay:1s}" )
    public Book getBook(String title) {
        // ...

Setting Retry via Configuration

@Retryable( attempts = "\${book.retry.attempts:3}",
            delay = "\${book.retry.delay:1s}")
    Book getBook(String title) {
        // ...

Setting Retry via Configuration

@Retryable(attempts = "\${book.retry.attempts:3}", delay = "\${book.retry.delay:1s}")
    open fun getBook(title: String): Book {
        // ...

With the above in place if book.retry.attempts is specified in configuration it wil be bound the value of the attempts member of the @Retryable annotation via annotation metadata.

Reactive Retry

@Retryable advice can also be applied to methods that return reactive types, such as an RxJava Flowable . For example:

Applying Retry Policy to Reactive Types

@Retryable
    public Flowable<Book> streamBooks() {
        // ...

Applying Retry Policy to Reactive Types

@Retryable
    Flowable<Book> streamBooks() {
        // ...

Applying Retry Policy to Reactive Types

@Retryable
    open fun streamBooks(): Flowable<Book> {
        // ...

In this case @Retryable advice will apply the retry policy to the reactive type.

Circuit Breaker

In a Microservice environment retry is useful, but in some cases excessive retries can overwhelm the system as clients repeatedly re-attempt failing operations.

The Circuit Breaker pattern is designed to resolve this issue by essentially allowing a certain number of failing requests and then opening a circuit that remains open for a period before allowing any additional retry attempts.

TheCircuitBreaker annotation is a variation of the @Retryable annotation that supports a reset member that indicates how long the circuit should remain open before it is reset (the default is 20 seconds).

Applying CircuitBreaker Advice

@CircuitBreaker(reset = "30s")
    public List<Book> findBooks() {
        // ...

Applying CircuitBreaker Advice

@CircuitBreaker(reset = "30s")
    List<Book> findBooks() {
        // ...

Applying CircuitBreaker Advice

@CircuitBreaker(reset = "30s")
    open fun findBooks(): List<Book> {
        // ...

The above example will retry to findBooks method 3 times and then open the circuit for 30 seconds, rethrowing the original exception and preventing potential downstream traffic such as HTTP requests and I/O operations flooding the system.

Bean Creation Retry

As mentioned previously, @Retryable advice is integrated right at the container level. This is useful as it is common problem in Microservices and environments like Docker where there may be a delay in services becoming available.

The following snippet is taken from the Neo4j driver support and demonstrates how bean creation can be wrapped in retry support:

@Factory <i data-value="1"></i><b>(1)</b>
public class Neo4jDriverFactory {
    ...
    @Retryable(ServiceUnavailableException.class) <i data-value="2"></i><b>(2)</b>
    @Bean(preDestroy = "close")
    public Driver buildDriver() {
        ...
    }
}
1 A factory bean is created that defines methods that create beans
2 The @Retryable annotation is used to catch ServiceUnavailableException and retry creating the driver before failing startup.

Retry Events

You can registerRetryEventListener instances as beans in order to listen forRetryEvent events that are published every time an operation is retried.

In addition, you can register event listeners forCircuitOpenEvent, when a circuit breaker circuit is opened, orCircuitClosedEvent for when a circuit is closed.

5.7 Scheduled Tasks

Like Spring and Grails, Micronaut features aScheduled annotation that can be used for scheduling background tasks.

Using the @Scheduled Annotation

TheScheduled annotation can be added to any method of a bean and you should set either the fixedRate , fixedDelay or cron members.

Remember that the scope of the bean has an impact on behaviour. a @Singleton bean will share state (the fields of the instance) each time the scheduled method is executed, while for a @Prototype bean a new instance is created for each execution.

Scheduling at a Fixed Rate

To schedule a task at a fixed rate, use the fixedRate member. For example:

Fixed Rate Example

@Scheduled(fixedRate = "5m")
    void everyFiveMinutes() {
        System.out.println("Executing everyFiveMinutes()");
    }

Fixed Rate Example

@Scheduled(fixedRate = "5m")
    void everyFiveMinutes() {
        System.out.println("Executing everyFiveMinutes()")
    }

Fixed Rate Example

@Scheduled(fixedRate = "5m")
    internal fun everyFiveMinutes() {
        println("Executing everyFiveMinutes()")
    }

The task above will execute every 5 minutes.

Scheduling with a Fixed Delay

To schedule a task so that it runs 5 minutes after the termination of the previous task use the fixedDelay member. For example:

Fixed Delay Example

@Scheduled(fixedDelay = "5m")
    void fiveMinutesAfterLastExecution() {
        System.out.println("Executing fiveMinutesAfterLastExecution()");
    }

Fixed Delay Example

@Scheduled(fixedDelay = "5m")
    void fiveMinutesAfterLastExecution() {
        System.out.println("Executing fiveMinutesAfterLastExecution()")
    }

Fixed Delay Example

@Scheduled(fixedDelay = "5m")
    internal fun fiveMinutesAfterLastExecution() {
        println("Executing fiveMinutesAfterLastExecution()")
    }

Scheduling a Cron Task

To schedule a Cron task use the cron member:

Cron Example

@Scheduled(cron = "0 15 10 ? * MON" )
    void everyMondayAtTenFifteenAm() {
        System.out.println("Executing everyMondayAtTenFifteenAm()");
    }

Cron Example

@Scheduled(cron = "0 15 10 ? * MON" )
    void everyMondayAtTenFifteenAm() {
        System.out.println("Executing everyMondayAtTenFifteenAm()")
    }

Cron Example

@Scheduled(cron = "0 15 10 ? * MON")
    internal fun everyMondayAtTenFifteenAm() {
        println("Executing everyMondayAtTenFifteenAm()")
    }

The above example will run the task every Monday morning at 10:15AM for the time zone of the server.

Scheduling with only an Initial Delay

To schedule a task so that it runs a single time after the server starts, use the initialDelay member:

Initial Delay Example

@Scheduled(initialDelay = "1m" )
    void onceOneMinuteAfterStartup() {
        System.out.println("Executing onceOneMinuteAfterStartup()");
    }

Initial Delay Example

@Scheduled(initialDelay = "1m" )
    void onceOneMinuteAfterStartup() {
        System.out.println("Executing onceOneMinuteAfterStartup()")
    }

Initial Delay Example

@Scheduled(initialDelay = "1m")
    internal fun onceOneMinuteAfterStartup() {
        println("Executing onceOneMinuteAfterStartup()")
    }

The above example will only run a single time 1 minute after the server starts.

Programmatically Scheduling Tasks

If you wish to programmatically schedule tasks, then you can use theTaskScheduler bean which can be injected as follows:

@Inject
    @Named(TaskExecutors.SCHEDULED)
    TaskScheduler taskScheduler;
@Inject
    @Named(TaskExecutors.SCHEDULED)
    TaskScheduler taskScheduler
@Inject
    @Named(TaskExecutors.SCHEDULED)
    lateinit var taskScheduler: TaskScheduler

Configuring Scheduled Tasks with Annotation Metadata

If you wish to make your application’s tasks configurable then you can use annotation metadata and property placeholder configuration to do so. For example:

Allow tasks to be configured

@Scheduled( fixedRate = "${my.task.rate:5m}",
                initialDelay = "${my.task.delay:1m}" )
    void configuredTask() {
        System.out.println("Executing configuredTask()");
    }

Allow tasks to be configured

@Scheduled( fixedRate = "\${my.task.rate:5m}",
                initialDelay = "\${my.task.delay:1m}" )
    void configuredTask() {
        System.out.println("Executing configuredTask()")
    }

Allow tasks to be configured

@Scheduled(fixedRate = "\${my.task.rate:5m}",
            initialDelay = "\${my.task.delay:1m}")
    internal fun configuredTask() {
        println("Executing configuredTask()")
    }

The above example allows the task execution frequency to be configured with the property my.task.rate and the initial delay to be configured with the property my.task.delay .

Configuring the Scheduled Task Thread Pool

Tasks executed by @Scheduled are by default run on a ScheduledExecutorService that is configured to have twice the number of threads as available processors.

You can configure this thread pool as desired using application.yml , for example:

Configuring Scheduled Task Thread Pool

micronaut:
    executors:
        scheduled:
            type: scheduled
            core-pool-size: 30
Table 1. Configuration Properties for UserExecutorConfiguration
Property Type Description

micronaut.executors.*.n-threads

java.lang.Integer

micronaut.executors.*.type

ExecutorType

Sets the executor type. Default value (SCHEDULED).

micronaut.executors.*.parallelism

java.lang.Integer

Sets the parallelism forWORK_STEALING. Default value (Number of processors available to the Java virtual machine).

micronaut.executors.*.core-pool-size

java.lang.Integer

Sets the core pool size forSCHEDULED. Default value (2 * Number of processors available to the Java virtual machine).

micronaut.executors.*.thread-factory-class

java.lang.Class

Sets the thread factory class.

micronaut.executors.*.number-of-threads

java.lang.Integer

Sets the number of threads forFIXED. Default value (2 * Number of processors available to the Java virtual machine).

Handling Exceptions

By default Micronaut includes a DefaultTaskExceptionHandler bean that implements theTaskExceptionHandler and simply logs the exception if an error occurs invoking a scheduled task.

If you have custom requirements you can replace this bean with a custom implementation (for example if you wish to send an email or shutdown context to fail fast). To do so simply write your ownTaskExceptionHandler and annotate it with @Replaces(DefaultTaskExceptionHandler.class) .

5.8 Bridging Spring AOP

Although Micronaut’s design is based on a compile time approach and does not rely on Spring dependency injection, there is still a lot of value in the Spring ecosystem that does not depend directly on the Spring container.

You may wish to use existing Spring projects within Micronaut and configure beans to be used within Micronaut.

You may also wish to leverage existing AOP advice from Spring. One example of this is Spring’s support for declarative transactions with @Transactional .

Micronaut provides support for Spring based transaction management without requiring Spring itself. You simply need to add the spring module to your application dependencies:

implementation("io.micronaut:micronaut-spring")
<dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-spring</artifactId>
</dependency>
If you use Micronaut’syou already get this dependency and a HibernateTransactionManager is configured for you.

This is done by defining a Micronaut@Transactional annotation that uses@AliasFor in a manner that every time you set a value with@Transactional it aliases the value to equivalent value in Spring’s version of @Transactional .

The benefit here is you can use Micronaut’s compile-time, reflection free AOP to declare programmatic Spring transactions. For example:

Using @Transactional

import io.micronaut.spring.tx.annotation.*;
...

@Transactional
public Book saveBook(String title) {
    ...
}
Micronaut’s version of@Transactional is also annotated with@Blocking ensuring that all methods annotated with use the I/O thread pool when executing within the HTTP server

6 The HTTP Server

Using the CLI

If you are creating your project using the Micronaut CLI’s create-app command, the http-server dependency is included by default.

Micronaut includes both non-blocking HTTP server and client APIs based on Netty .

The design of the HTTP server in Micronaut is optimized for interchanging messages between Microservices, typically in JSON, and is not intended as a full server-side MVC framework. For example, there is currently no support for server-side views or features typical of a traditional server-side MVC framework.

The goal of the HTTP server is to make it as easy as possible to expose APIs that can be consumed by HTTP clients, whatever language they may be written in. To use the HTTP server you must have the http-server-netty dependency on your classpath.

implementation("io.micronaut:micronaut-http-server-netty")
<dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-http-server-netty</artifactId>
</dependency>

A "Hello World" server application can be seen below:

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

@Controller("/hello") <b>(1)</b>
public class HelloController {

    @Get(produces = MediaType.TEXT_PLAIN) <b>(2)</b>
    public String index() {
        return "Hello World"; <b>(3)</b>
    }
}
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get

@Controller('/hello') <b>(1)</b>
class HelloController {

    @Get(produces = MediaType.TEXT_PLAIN) <b>(2)</b>
    String index() {
        'Hello World' <b>(3)</b>
    }
}
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get

@Controller("/hello") <b>(1)</b>
class HelloController {

    @Get(produces = [MediaType.TEXT_PLAIN]) <b>(2)</b>
    fun index(): String {
        return "Hello World" <b>(3)</b>
    }
}
1 The class is defined as a controller with the@Controller annotation mapped to the path /hello
2 The method will respond to a GET request to /hello and returns a response with a text/plain content type
3 By defining method called index , by convention the method is exposed via the /hello URI

6.1 Running the Embedded Server

To run the server simply create an Application class with a static void main method. For example:

import io.micronaut.runtime.Micronaut;

public class Application {

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

class Application {

    static void main(String... args) {
        Micronaut.run Application.class
    }
}
import io.micronaut.runtime.Micronaut

object Application {

    @JvmStatic
    fun main(args: Array<String>) {
        Micronaut.run(Application.javaClass)
    }
}

To run the application from a unit test you can use theEmbeddedServer interface:

import io.micronaut.context.annotation.Property;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.runtime.server.EmbeddedServer;
import io.micronaut.test.annotation.MicronautTest;
import org.junit.jupiter.api.Test;

import javax.inject.Inject;

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

@MicronautTest
public class HelloControllerSpec {
    @Inject
    EmbeddedServer server; <b>(1)</b>

    @Inject
    @Client("/")
    HttpClient client; <b>(2)</b>

    @Test
    void testHelloWorldResponse() {
        String response = client.toBlocking() <b>(3)</b>
                .retrieve(HttpRequest.GET("/hello"));
        assertEquals("Hello World", response); //) <b>(4)</b>
    }
}
import io.micronaut.http.HttpRequest
import io.micronaut.http.client.HttpClient
import io.micronaut.http.client.annotation.Client
import io.micronaut.runtime.server.EmbeddedServer
import io.micronaut.test.annotation.MicronautTest
import spock.lang.Specification
import javax.inject.Inject

@MicronautTest
class HelloControllerSpec extends Specification {
    @Inject
    EmbeddedServer embeddedServer <b>(1)</b>

    @Inject
    @Client("/")
    HttpClient client <b>(2)</b>

    void "test hello world response"() {
        expect:
            client.toBlocking() <b>(3)</b>
                    .retrieve(HttpRequest.GET('/hello')) == "Hello World" <b>(4)</b>
    }
}
import io.micronaut.context.annotation.Property
import io.micronaut.http.client.HttpClient
import io.micronaut.http.client.annotation.Client
import io.micronaut.runtime.server.EmbeddedServer
import io.micronaut.test.annotation.MicronautTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import javax.inject.Inject

@MicronautTest
class HelloControllerSpec {

    @Inject
    lateinit var server: EmbeddedServer <b>(1)</b>

    @Inject
    @field:Client("/")
    lateinit var client: HttpClient <b>(2)</b>

    @Test
    fun testHelloWorldResponse() {
        val rsp: String = client.toBlocking() <b>(3)</b>
                .retrieve("/hello")
        assertEquals("Hello World", rsp) <b>(4)</b>
    }
}
1 The EmbeddedServer is run and Spock’s @AutoCleanup annotation ensures the server is stopped after the specification completes.
2 The EmbeddedServer interface provides the URL of the server under test which runs on a random port.
3 The test uses the Micronaut http client to make the call
4 The retrieve method returns the response of the controller as a String
Without explicit port configuration, the port will be 8080, unless the application is run under the test environment. In that case the port will be random. When the application context is started from the context of a test class, the test environment is added automatically.

6.2 Running Server on a Specific Port

By default the server runs on port 8080. However, you can set the server to run on a specific port:

micronaut:
  server:
    port: 8086
This is also possible to be configured from an environment variable: MICRONAUT_SERVER_PORT=8086

To run on a random port:

micronaut:
  server:
    port: -1

6.3 HTTP Routing

The@Controller annotation used in the previous section is one ofseveral annotations that allow you to control the construction of HTTP routes.

URI Paths

The value of the @Controller annotation is a RFC-6570 URI template you can therefore embed URI variables within the path using the syntax defined by the URI template specification.

Many other frameworks, including Spring, implement the URI template specification

The actual implementation is handled by theUriMatchTemplate class, which extendsUriTemplate.

You can use this class explicitly within your application to build URIs. For example:

Using a UriTemplate

UriMatchTemplate template = UriMatchTemplate.of("/hello/{name}");

        assertTrue(template.match("/hello/John").isPresent()); <b>(1)</b>
        assertEquals("/hello/John", template.expand(  <b>(2)</b>
                Collections.singletonMap("name", "John")
        ));

Using a UriTemplate

given:
        UriMatchTemplate template = UriMatchTemplate.of("/hello/{name}")

        expect:
        template.match("/hello/John").isPresent() <b>(1)</b>
        template.expand(["name": "John"]) == "/hello/John" <b>(2)</b>

Using a UriTemplate

val template = UriMatchTemplate.of("/hello/{name}")

        assertTrue(template.match("/hello/John").isPresent) <b>(1)</b>
        assertEquals("/hello/John", template.expand(mapOf("name" to "John")))  <b>(2)</b>
1 The match method can be used to match a path
2 The expand method can be used to expand a template into a URI.

If you have a requirement to build paths to include in your responses you can useUriTemplate to do so.

URI Path Variables

URI variables can be referenced via method arguments. For example:

URI Variables Example

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

@Controller("/issues") <b>(1)</b>
public class IssuesController {

    @Get("/{number}") <b>(2)</b>
    public String issue(@PathVariable Integer number) { <b>(3)</b>
        return "Issue # " + number + "!"; <b>(4)</b>
    }
}

URI Variables Example

import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.annotation.PathVariable

@Controller("/issues") <b>(1)</b>
class IssuesController {

    @Get("/{number}") <b>(2)</b>
    String issue(@PathVariable Integer number) { <b>(3)</b>
        "Issue # " + number + "!" <b>(4)</b>
    }
}

URI Variables Example

import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.annotation.PathVariable

@Controller("/issues") <b>(1)</b>
class IssuesController {

    @Get("/{number}") <b>(2)</b>
    fun issue(@PathVariable number: Int): String { <b>(3)</b>
        return "Issue # $number!" <b>(4)</b>
    }
}
1 The @Controller annotation is specified with a base URI of /issues
2 TheGet annotation is used to map the method to an HTTPGET with a URI variable embedded in the URI called number
3 The method argument can be optionally annotated withPathVariable
4 The value of the URI variable is referenced in the implementation

Micronaut will map the URI /issues/{number} for the above controller. We can assert this is the case by writing a set of unit tests:

Testing URI Variables

import io.micronaut.context.ApplicationContext;
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.exceptions.HttpClientResponseException;
import io.micronaut.runtime.server.EmbeddedServer;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

public class IssuesControllerTest {

    private static EmbeddedServer server;
    private static HttpClient client;

    @BeforeClass <b>(1)</b>
    public static void setupServer() {
        server = ApplicationContext.run(EmbeddedServer.class);
        client = server
                    .getApplicationContext()
                    .createBean(HttpClient.class, server.getURL());
    }

    @AfterClass <b>(2)</b>
    public static void stopServer() {
        if(server != null) {
            server.stop();
        }
        if(client != null) {
            client.stop();
        }
    }

    @Test
    public void testIssue() throws Exception {
        String body = client.toBlocking().retrieve("/issues/12"); <b>(3)</b>

        assertNotNull(body);
        assertEquals("Issue # 12!", body); <b>(4)</b>
    }

    @Test
    public void testShowWithInvalidInteger() {
        HttpClientResponseException e =Assertions.assertThrows(HttpClientResponseException.class, () ->
                client.toBlocking().exchange("/issues/hello"));

        assertEquals(400, e.getStatus().getCode()); <b>(5)</b>
    }

    @Test
    public void testIssueWithoutNumber() {
        HttpClientResponseException e = Assertions.assertThrows(HttpClientResponseException.class, () ->
                client.toBlocking().exchange("/issues/"));

        assertEquals(404, e.getStatus().getCode()); <b>(6)</b>
    }
}

Testing URI Variables

import io.micronaut.context.ApplicationContext
import io.micronaut.http.client.HttpClient
import io.micronaut.http.client.exceptions.HttpClientResponseException
import io.micronaut.runtime.server.EmbeddedServer
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification

class IssuesControllerTest extends Specification {

    @Shared
    @AutoCleanup <b>(2)</b>
    EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer) <b>(1)</b>

    @Shared
    @AutoCleanup <b>(2)</b>
    HttpClient client = HttpClient.create(embeddedServer.URL) <b>(1)</b>

    void "test issue"() {
        when:
        String body = client.toBlocking().retrieve("/issues/12") <b>(3)</b>

        then:
        body != null
        body == "Issue # 12!" <b>(4)</b>
    }

    void "/issues/{number} with an invalid Integer number responds 400"() {
        when:
        client.toBlocking().exchange("/issues/hello")

        then:
        HttpClientResponseException e = thrown(HttpClientResponseException)
        e.status.code == 400 <b>(5)</b>
    }

    void "/issues/{number} without number responds 404"() {
        when:
        client.toBlocking().exchange("/issues/")

        then:
        HttpClientResponseException e = thrown(HttpClientResponseException)
        e.status.code == 404 <b>(6)</b>
    }
}

Testing URI Variables

import io.micronaut.context.ApplicationContext
import io.micronaut.http.client.exceptions.HttpClientResponseException
import io.micronaut.runtime.server.EmbeddedServer
import io.kotlintest.shouldBe
import io.kotlintest.shouldNotBe
import io.kotlintest.shouldThrow
import io.kotlintest.specs.StringSpec
import io.micronaut.http.client.RxHttpClient

class IssuesControllerTest: StringSpec() {

    val embeddedServer = autoClose( <b>(2)</b>
            ApplicationContext.run(EmbeddedServer::class.java) <b>(1)</b>
    )

    val client = autoClose( <b>(2)</b>
            embeddedServer.applicationContext.createBean(RxHttpClient::class.java, embeddedServer.getURL()) <b>(1)</b>
    )

    init {
        "test issue" {
            val body = client.toBlocking().retrieve("/issues/12") <b>(3)</b>

            body shouldNotBe null
            body shouldBe "Issue # 12!" <b>(4)</b>
        }

        "test issue with invalid integer" {
            val e = shouldThrow<HttpClientResponseException> { client.toBlocking().exchange<Any>("/issues/hello") }

            e.status.code shouldBe 400 <b>(5)</b>
        }

        "test issue without number" {
            val e = shouldThrow<HttpClientResponseException> { client.toBlocking().exchange<Any>("/issues/") }

            e.status.code shouldBe 404 <b>(6)</b>
        }
    }
}
1 The embedded server and http client is being started
2 The server and client will be cleaned up after all of the tests have finished
3 The tests sends a request to the URI /issues/12
4 And then asserts the response is "Issue # 12"
5 Another test asserts a 400 response is returned when an invalid number is sent in the URL
6 Another test asserts a 404 response is returned when no number is provided in the URL. The variable being present is required in order for the route to be executed.

Note that the URI template in the previous example requires that the number variable is specified. You can specify optional URI templates with the syntax: /issues{/number} and by annotating the number parameter with @Nullable .

The following table provides some examples of URI templates and what they match:

Table 1. URI Template Matching
Template Description Matching URI

/books/{id}

Simple match

/books/1

/books/{id:2}

A variable of 2 characters max

/books/10

/books{/id}

An optional URI variable

/books/10 or /books

/book{/id:[a-zA-Z]+}

An optional URI variable with regex

/books/foo

/books{?max,offset}

Optional query parameters

/books?max=10&offset=10

/books{/path:.*}{.ext}

Regex path match with extension

/books/foo/bar.xml

URI Reserved Character Matching

By default URI variables as defined by the RFC-6570 URI template spec cannot include reserved characters such as / , ? etc.

If you wish to match or expand entire paths then this can be problematic. As per section 3.2.3 of the specification , you can use reserved expansion or matching using the + operator.

For example the URI /books/{+path} will match both /books/foo and /books/foo/bar since the + indicates that the variable path should include reserved characters (in this case / ).

Routing Annotations

The previous example used the@Get annotation to add method that accepted HTTPGET requests. The following tables summarizes the available annotations and how they map to the different HTTP methods:

Table 2. HTTP Routing Annotations
Annotation HTTP Method

@Delete

DELETE

@Get

GET

@Head

HEAD

@Options

OPTIONS

@Patch

PATCH

@Put

PUT

@Post

POST

@Trace

TRACE

All of the method annotations default to / .

Multiple URIs

Each of the routing annotations support multiple URI templates. For each template, a route will be created. This feature is useful for example to change the path of the API and leaving the existing path as is for backwards compatibility. For example:

Multiple URIs

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

@Controller("/hello")
public class BackwardCompatibleController {

    @Get(uris = {"/{name}", "/person/{name}"}) <b>(1)</b>
    public String hello(String name) { <b>(2)</b>
        return "Hello, " + name;
    }
}

Multiple URIs

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

@Controller("/hello")
class BackwardCompatibleController {

    @Get(uris = ["/{name}", "/person/{name}"]) <b>(1)</b>
    String hello(String name) { <b>(2)</b>
        "Hello, " + name
    }
}

Multiple URIs

@Controller("/hello")
class BackwardCompatibleController {

    @Get(uris = ["/{name}", "/person/{name}"]) <b>(1)</b>
    fun hello(name: String): String { <b>(2)</b>
        return "Hello, $name"
    }
}
1 Specify multiple templates
2 Bind to the template arguments as normal
Route validation is more complicated with multiple templates. If a variable that would normally be required does not exist in all templates, then that variable is considered optional since it may not exist for every execution of the method.

Building Routes Programmatically

If you prefer to not use annotations and declare all of your routes in code then never fear, Micronaut has a flexibleRouteBuilder API that makes it a breeze to define routes programmatically.

To start off with you should subclassDefaultRouteBuilder and then simply inject the controller you wish to route to into the method and define your routes:

URI Variables Example

import io.micronaut.context.ExecutionHandleLocator;
import io.micronaut.web.router.DefaultRouteBuilder;
import javax.inject.Inject;
import javax.inject.Singleton;

@Singleton
public class MyRoutes extends DefaultRouteBuilder { <b>(1)</b>

    public MyRoutes(ExecutionHandleLocator executionHandleLocator, UriNamingStrategy uriNamingStrategy) {
        super(executionHandleLocator, uriNamingStrategy);
    }

    @Inject
    void issuesRoutes(IssuesController issuesController) { <b>(2)</b>
        GET("/issues/show/{number}", issuesController, "issue", Integer.class); <b>(3)</b>
    }
}

URI Variables Example

import io.micronaut.web.router.GroovyRouteBuilder

import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class MyRoutes extends GroovyRouteBuilder { <b>(1)</b>

    MyRoutes(ExecutionHandleLocator executionHandleLocator, UriNamingStrategy uriNamingStrategy, ConversionService<?> conversionService) {
        super(executionHandleLocator, uriNamingStrategy, conversionService)
    }

    @Inject
    void issuesRoutes(IssuesController issuesController) { <b>(2)</b>
        GET("/issues/show/{number}", issuesController.&issue) <b>(3)</b>
    }
}

URI Variables Example

import io.micronaut.context.ExecutionHandleLocator
import io.micronaut.web.router.DefaultRouteBuilder
import io.micronaut.web.router.RouteBuilder

import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class MyRoutes(executionHandleLocator: ExecutionHandleLocator, uriNamingStrategy: RouteBuilder.UriNamingStrategy) :
        DefaultRouteBuilder(executionHandleLocator, uriNamingStrategy) { <b>(1)</b>

    @Inject
    fun issuesRoutes(issuesController: IssuesController) { <b>(2)</b>
        GET("/issues/show/{number}", issuesController, "issue", Int::class.java) <b>(3)</b>
    }
}
1 Route definitions should subclassDefaultRouteBuilder
2 Use @Inject to inject a method with the controller you want to route to
3 Use methods such asGET to route to controller methods. Note that even though the issues controller is being used, the route has no knowledge of its @Controller annotation and thus the full path must be specified.
Unfortunately due to type erasure a Java method lambda reference cannot be used with the API. For Groovy there is a GroovyRouteBuilder class which can be subclassed that allows passing Groovy method references.

Route Compile Time Validation

Micronaut supports validating route arguments at compile time with the validation library. To get started simply add the validation dependency to your build:

build.gradle

annotationProcessor "io.micronaut:micronaut-validation" // Java only
kapt "io.micronaut:micronaut-validation" // Kotlin only
compile "io.micronaut:micronaut-validation"

With the correct dependency on your classpath, route arguments will automatically be checked at compile time. The compilation will fail if any of the following conditions are met:

  • The URI template contains a variable that is optional, but the method parameter is not annotated with @Nullable or is an java.util.Optional .

An optional variable is one that will allow the route to match a URI even if the value is not present. For example /foo{/bar} will match requests to /foo and /foo/abc . The non optional variant would be /foo/{bar} . See thesection for more information.

  • The URI template contains a variable that is missing from the method arguments.

To disable route compile time validation, set the system property -Dmicronaut.route.validation=false . For Java and Kotlin users using Gradle, the same effect can be achieved by removing the validation dependency from the annotationProcessor / kapt scope.

Routing non-standard HTTP methods

It may be necessary to support a non-standard HTTP method for a client or server. Specifications like RFC-4918 Webdav require additional methods like REPORT or LOCK for example. To support that use case the @CustomHttpMethod annotation can be used.

RoutingExample

@CustomHttpMethod(method = "LOCK", value = "/{name}")
String lock(String name)

The annotation can be used anywhere any of the standard method annotations can be used, including controllers and declarative http clients.

6.4 Simple Request Binding

The examples in the previous section demonstrates how Micronaut allows you to bind method parameters from URI path variables. This section will discuss how to bind arguments from other parts of the request.

Binding Annotations

All of the binding annotations support customization of the name of the variable being bound from with their name member.

The following table summarizes the annotations, their purpose and provides an example:

Table 1. Parameter Binding Annotations
Annotation Description Example

@Body

Binds from the body of the request

@Body String body

@CookieValue

Binds a parameter from a cookie

@CookieValue String myCookie

@Header

Binds a parameter from an HTTP header

@Header String contentType

@QueryValue

Binds from a request query parameter

@QueryValue String myParam

@Part

Binds from a part of a multipart request

@Part CompletedFileUpload file

@RequestAttribute

Binds from an attribute of the request. Attributes are typically created in filters

@RequestAttribute String myAttribute

@PathVariable

Binds from the path of the request

@PathVariable String id

@RequestBean

Binds any Bindable value to single Bean object

@RequestBean MyBean bean

When a value is not specified to any binding annotation then the parameter name is used. In other words the following two methods are equivalent and both bind from a cookie called myCookie :

@Get("/cookieName")
    public String cookieName(@CookieValue("myCookie") String myCookie) {
        // ...
    }

    @Get("/cookieInferred")
    public String cookieInferred(@CookieValue String myCookie) {
        // ...
    }
@Get("/cookieName")
    String cookieName(@CookieValue("myCookie") String myCookie) {
        // ...
    }

    @Get("/cookieInferred")
    String cookieInferred(@CookieValue String myCookie) {
        // ...
    }
@Get("/cookieName")
    fun cookieName(@CookieValue("myCookie") myCookie: String): String {
        // ...
    }

    @Get("/cookieInferred")
    fun cookieInferred(@CookieValue myCookie: String): String {
        // ...
    }

Because hyphens are not allowed in variable names it may be necessary to set the name in the annotation. The following two definitions are equivalent:

@Get("/headerName")
    public String headerName(@Header("Content-Type") String contentType) {
        // ...
    }

    @Get("/headerInferred")
    public String headerInferred(@Header String contentType) {
        // ...
    }
@Get("/headerName")
    String headerName(@Header("Content-Type") String contentType) {
        // ...
    }

    @Get("/headerInferred")
    String headerInferred(@Header String contentType) {
        // ...
    }
@Get("/headerName")
    fun headerName(@Header("Content-Type") contentType: String): String {
        // ...
    }

    @Get("/headerInferred")
    fun headerInferred(@Header contentType: String): String {
        // ...
    }

Binding from Multiple Query values

Instead of binding from a single section of the request, it may be desirable to bind all query values for example to a POJO. This can be achieved by using the exploded operator ( ?pojo* ) in the URI template. For example:

Binding Request parameters to POJO

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

import edu.umd.cs.findbugs.annotations.Nullable;
import javax.validation.Valid;

@Controller("/api")
public class BookmarkController {

    @Get("/bookmarks/list{?paginationCommand*}")
    public HttpStatus list(@Valid @Nullable PaginationCommand paginationCommand) {
        return HttpStatus.OK;
    }
}

Binding Request parameters to POJO

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

import javax.annotation.Nullable
import javax.validation.Valid

@Controller("/api")
class BookmarkController {

    @Get("/bookmarks/list{?paginationCommand*}")
    HttpStatus list(@Valid @Nullable PaginationCommand paginationCommand) {
        HttpStatus.OK
    }
}

Binding Request parameters to POJO

import io.micronaut.http.HttpStatus
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.validation.Validated
import javax.validation.Valid

@Controller("/api")
open class BookmarkController {

    @Get("/bookmarks/list{?paginationCommand*}")
    open fun list(@Valid paginationCommand: PaginationCommand): HttpStatus {
        return HttpStatus.OK
    }
}

Binding from Multiple Bindable values

Instead of binding just Query values, it is also possible to bind any Bindable value to POJO (e.g. when you want to bind HttpRequest , @PathVariable , @QueryValue and @Header to single POJO). This can be achieved with @RequestBean annotation and custom Bean class that has fields with Bindable annotations or fields that can be bound by type (e.g. HttpRequest, BasicAuth, Authentication etc.). For example:

Binding Bindable values to POJO

@Controller("/api")
public class MovieTicketController {

    // You can also omit query parameters like:
    // @Get("/movie/ticket/{movieId}
    @Get("/movie/ticket/{movieId}{?minPrice,maxPrice}")
    public HttpStatus list(@Valid @RequestBean MovieTicketBean bean) {
        return HttpStatus.OK;
    }
}

Binding Bindable values to POJO

@Controller("/api")
class MovieTicketController {

    // You can also omit query parameters like:
    // @Get("/movie/ticket/{movieId}
    @Get("/movie/ticket/{movieId}{?minPrice,maxPrice}")
    HttpStatus list(@Valid @RequestBean MovieTicketBean bean) {
        return HttpStatus.OK
    }
}

Binding Bindable values to POJO

@Controller("/api")
open class MovieTicketController {

    // You can also omit query parameters like:
    // @Get("/movie/ticket/{movieId}
    @Get("/movie/ticket/{movieId}{?minPrice,maxPrice}")
    open fun list(@Valid @RequestBean bean: MovieTicketBean): HttpStatus {
        return HttpStatus.OK
    }

}

Bean definition

@Introspected
public class MovieTicketBean {

    private HttpRequest<?> httpRequest;

    @PathVariable
    private String movieId;

    @Nullable
    @QueryValue
    @PositiveOrZero
    private Double minPrice;

    @Nullable
    @QueryValue
    @PositiveOrZero
    private Double maxPrice;

    public MovieTicketBean(HttpRequest<?> httpRequest, String movieId, Double minPrice, Double maxPrice) {
        this.httpRequest = httpRequest;
        this.movieId = movieId;
        this.minPrice = minPrice;
        this.maxPrice = maxPrice;
    }

    public HttpRequest<?> getHttpRequest() {
        return httpRequest;
    }

    public String getMovieId() {
        return movieId;
    }

    @Nullable
    public Double getMaxPrice() {
        return maxPrice;
    }

    @Nullable
    public Double getMinPrice() {
        return minPrice;
    }
}

Bean definition

@Introspected
class MovieTicketBean {

    private HttpRequest<?> httpRequest

    @PathVariable
    String movieId

    @Nullable
    @QueryValue
    @PositiveOrZero
    Double minPrice

    @Nullable
    @QueryValue
    @PositiveOrZero
    Double maxPrice
}

Bean definition

@Introspected
data class MovieTicketBean(val httpRequest: HttpRequest<Any>,
                           @field:PathVariable val movieId: String,
                           @field:QueryValue @field:PositiveOrZero @field:Nullable val minPrice: Double,
                           @field:QueryValue @field:PositiveOrZero @field:Nullable val maxPrice: Double)

Bean class has to be introspected with @Introspected . It can be one of:

  1. Mutable Bean class with setters and getters

  2. Immutable Bean class with getters and all argument constructor (or @Creator annotation on a constructor or static method). Arguments of constructor have to match field names so object can be constructed without reflection.

Since Java does not retain argument names in the byte code, you have to compile code with -parameters in case you want to use Immutable Bean class from another Jar. Another option is to extend Bean class in your source.

Bindable Types

Generally any type that can be converted from a String representation to a Java type via theConversionService API can be bound to.

This includes most common Java types, however additionalTypeConverter instances can be registered simply be creating @Singleton beans of type TypeConverter .

The handling of nullability deserves special mention. Consider for example the following example:

@Get("/headerInferred")
    public String headerInferred(@Header String contentType) {
        // ...
    }
@Get("/headerInferred")
    String headerInferred(@Header String contentType) {
        // ...
    }
@Get("/headerInferred")
    fun headerInferred(@Header contentType: String): String {
        // ...
    }

In this case if the HTTP header Content-Type is not present in the request the route is considered invalid, since it cannot be satisfied and a HTTP 400 BAD REQUEST is returned.

If you wish for the Content-Type header to be optional, you can instead write:

@Get("/headerNullable")
    public String headerNullable(@Nullable @Header String contentType) {
        // ...
    }
@Get("/headerNullable")
    String headerNullable(@Nullable @Header String contentType) {
        // ...
    }
@Get("/headerNullable")
    fun headerNullable(@Header contentType: String?): String? {
        // ...
    }

An null string will be passed if the header is absent from the request.

java.util.Optional can also be used, however that is discouraged for method parameters.

Additionally, any DateTime that conforms to RFC-1123 can be bound to a parameter, alternatively the format can be customized with theFormat annotation:

@Get("/date")
    public String date(@Header ZonedDateTime date) {
        // ...
    }

    @Get("/dateFormat")
    public String dateFormat(@Format("dd/MM/yyyy hh:mm:ss a z") @Header ZonedDateTime date) {
        // ...
    }
@Get("/date")
    String date(@Header ZonedDateTime date) {
        // ...
    }

    @Get("/dateFormat")
    String dateFormat(@Format("dd/MM/yyyy hh:mm:ss a z") @Header ZonedDateTime date) {
        // ...
    }
@Get("/date")
    fun date(@Header date: ZonedDateTime): String {
        // ...
    }

    @Get("/dateFormat")
    fun dateFormat(@Format("dd/MM/yyyy hh:mm:ss a z") @Header date: ZonedDateTime): String {
        // ...
    }

Type Based Binding Parameters

Some parameters are recognized by their type instead of their annotation. The following table summarizes the parameter types, their purpose, and provides an example:

Type Description Example

BasicAuth

Allows binding of basic authorization credentials

BasicAuth basicAuth

Variables resolution

Micronaut will try to populate method arguments in the following order:

  1. URI variables like /{id} .

  2. If the request is a GET request from query parameters (ie. ?foo=bar ).

  3. If there is a @Body and request allows the body, bind the body to it.

  4. if the request can have a body and no @Body is defined then try parse the body (either JSON or form data) and bind the method arguments from the body.

  5. Finally, if the method arguments cannot be populated return 400 BAD REQUEST .

6.5 Host Resolution

You may need to resolve the host name of the current server. Micronaut ships with an implementation of theHttpHostResolver interface.

The default implementation will look for host information in the following places in order:

  1. The supplied configuration

  2. The Forwarded header

  3. The X-Forwarded- headers

  4. The Host header

  5. The properties on the request URI

  6. The properties on the embedded server URI

The behavior of which headers to pull the relevant data can be changed with the following configuration:

Table 1. Configuration Properties for HttpServerConfiguration$HostResolutionConfiguration
Property Type Description

micronaut.server.host-resolution.host-header

java.lang.String

The header name that stores the host

micronaut.server.host-resolution.protocol-header

java.lang.String

The header name that stores the protocol

micronaut.server.host-resolution.port-header

java.lang.String

The header name that stores the port

micronaut.server.host-resolution.port-in-host

boolean

True if the host header supports a port

6.6 Client IP Address

You may need to resolve the originating IP address of an HTTP Request. Micronaut ships with an implementation of HttpClientAddressResolver .

The default implementation will resolve the client address in the following places in order:

  1. The configured header

  2. The Forwarded header

  3. The X-Forwarded-For header

  4. The remote address on the request

The first priority header name can be configured with micronaut.server.client-address-header .

6.7 The HttpRequest and HttpResponse

If you need more control over request processing then you can instead write a method that receives the completeHttpRequest.

In fact, there are several higher level interfaces that can be bound to method parameters of controllers. These include:

Table 1. Bindable Micronaut Interfaces
Interface Description Example

HttpRequest

The full HttpRequest

String hello(HttpRequest request)

HttpHeaders

All HTTP headers present in the request

String hello(HttpHeaders headers)

HttpParameters

All HTTP parameters (either from URI variables or request parameters) present in the request

String hello(HttpParameters params)

Cookies

All the Cookies present in the request

String hello(Cookies cookies)

TheHttpRequest should be declared parametrized with a concrete generic type if the request body is needed, e.g. HttpRequest<MyClass> request . The body may not be available from the request otherwise.

In addition, for full control over the emitted HTTP response you can use the static factory methods of theHttpResponse class which return aMutableHttpResponse.

The following example implements the previous MessageController example using theHttpRequest andHttpResponse objects:

Request and Response Example

import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;

@Controller("/request")
public class MessageController {

    @Get("/hello") <b>(1)</b>
    public HttpResponse<String> hello(HttpRequest<?> request) {
        String name = request.getParameters()
                             .getFirst("name")
                             .orElse("Nobody"); <b>(2)</b>

        return HttpResponse.ok("Hello " + name + "!!")
                 .header("X-My-Header", "Foo"); <b>(3)</b>
    }
}

Request and Response Example

import io.micronaut.http.HttpRequest
import io.micronaut.http.HttpResponse
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get

@Controller("/request")
class MessageController {

    @Get("/hello") <b>(1)</b>
    HttpResponse<String> hello(HttpRequest<?> request) {
        String name = request.getParameters()
                             .getFirst("name")
                             .orElse("Nobody") <b>(2)</b>

        HttpResponse.ok("Hello " + name + "!!")
                 .header("X-My-Header", "Foo") <b>(3)</b>
    }
}

Request and Response Example

import io.micronaut.http.HttpRequest
import io.micronaut.http.HttpResponse
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get

@Controller("/request")
class MessageController {

    @Get("/hello") <b>(1)</b>
    fun hello(request: HttpRequest<*>): HttpResponse<String> {
        val name = request.parameters
                .getFirst("name")
                .orElse("Nobody") <b>(2)</b>

        return HttpResponse.ok("Hello $name!!")
                .header("X-My-Header", "Foo") <b>(3)</b>
    }
}
1 The method is mapped to the URI /hello and accepts aHttpRequest
2 TheHttpRequest is used to obtain the value of a query parameter called name .
3 TheHttpResponse.ok(T) method is used to return aMutableHttpResponse with a text body. A header called X-My-Header is also added to the response object.

HttpRequest is also available from static context viaServerRequestContext.

GenerallyServerRequestContext is available within reactive flow, but the recommended approach is to propagate the necessary state through lambdas.

6.8 Response Status

A Micronaut’s controller action responds with a 200 HTTP status code by default.

If the controller’s action returns a HttpResponse object, you can configure the status code for the response object with the status method.

@Get(value = "/http-response", produces = MediaType.TEXT_PLAIN)
    public HttpResponse httpResponse() {
        return HttpResponse.status(HttpStatus.CREATED).body("success");
    }
@Get(value = "/http-response", produces = MediaType.TEXT_PLAIN)
    HttpResponse httpResponse() {
        HttpResponse.status(HttpStatus.CREATED).body("success")
    }
@Get(value = "/http-response", produces = [MediaType.TEXT_PLAIN])
    fun httpResponse(): HttpResponse<String> {
        return HttpResponse.status<String>(HttpStatus.CREATED).body("success")
    }

You can also use the @Status annotation.

@Status(HttpStatus.CREATED)
    @Get(produces = MediaType.TEXT_PLAIN)
    public String index() {
        return "success";
    }
@Status(HttpStatus.CREATED)
    @Get(produces = MediaType.TEXT_PLAIN)
    String index() {
        return "success"
    }
@Status(HttpStatus.CREATED)
    @Get(produces = [MediaType.TEXT_PLAIN])
    fun index(): String {
        return "success"
    }

or even respond with an HttpStatus

@Get("/http-status")
    public HttpStatus httpStatus() {
        return HttpStatus.CREATED;
    }
@Get("/http-status")
    HttpStatus httpStatus() {
        HttpStatus.CREATED
    }
@Get("/http-status")
    fun httpStatus(): HttpStatus {
        return HttpStatus.CREATED
    }

6.9 Response Content-Type

A Micronaut’s controller action produces application/json by default. Nonetheless you can change the Content-Type of the response with the @Produces annotation or the produces member of the HTTP method annotations.

import io.micronaut.context.annotation.Requires;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Produces;

@Controller("/produces")
public class ProducesController {

    @Get <b>(1)</b>
    public HttpResponse index() {
        return HttpResponse.ok().body("{\"msg\":\"This is JSON\"}");
    }

    @Produces(MediaType.TEXT_HTML)
    @Get("/html") <b>(2)</b>
    public String html() {
        return "<html><title><h1>HTML</h1></title><body></body></html>";
    }

    @Get(value = "/xml", produces = MediaType.TEXT_XML) <b>(3)</b>
    public String xml() {
        return "<html><title><h1>XML</h1></title><body></body></html>";
    }
}
import io.micronaut.context.annotation.Requires
import io.micronaut.http.HttpResponse
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.annotation.Produces

@Controller("/produces")
class ProducesController {

    @Get <b>(1)</b>
    HttpResponse index() {
        HttpResponse.ok().body("{\"msg\":\"This is JSON\"}")
    }

    @Produces(MediaType.TEXT_HTML) <b>(2)</b>
    @Get("/html")
    String html() {
        "<html><title><h1>HTML</h1></title><body></body></html>"
    }

    @Get(value = "/xml", produces = MediaType.TEXT_XML) <b>(3)</b>
    String xml() {
        return "<html><title><h1>XML</h1></title><body></body></html>"
    }
}
import io.micronaut.context.annotation.Requires
import io.micronaut.http.HttpResponse
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.annotation.Produces

@Controller("/produces")
class ProducesController {

    @Get <b>(1)</b>
    fun index(): HttpResponse<*> {
        return HttpResponse.ok<Any>().body("{\"msg\":\"This is JSON\"}")
    }

    @Produces(MediaType.TEXT_HTML)
    @Get("/html") <b>(2)</b>
    fun html(): String {
        return "<html><title><h1>HTML</h1></title><body></body></html>"
    }

    @Get(value = "/xml", produces = [MediaType.TEXT_XML]) <b>(3)</b>
    fun xml(): String {
        return "<html><title><h1>XML</h1></title><body></body></html>"
    }
}
1 The default content type is JSON
2 Annotate a controller’s action with @Produces to change the response content type.
3 Setting the produces member of the method annotation also changes the content type.

6.10 Accepted Request Content-Type

A Micronaut’s controller action consumes application/json by default. Consuming other content types is supported with the @Consumes annotation, or the consumes member of any HTTP method annotation.

import io.micronaut.context.annotation.Requires;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Consumes;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Post;

@Controller("/consumes")
public class ConsumesController {

    @Post <b>(1)</b>
    public HttpResponse index() {
        return HttpResponse.ok();
    }

    @Consumes({MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON}) <b>(2)</b>
    @Post("/multiple")
    public HttpResponse multipleConsumes() {
        return HttpResponse.ok();
    }

    @Post(value = "/member", consumes = MediaType.TEXT_PLAIN) <b>(3)</b>
    public HttpResponse consumesMember() {
        return HttpResponse.ok();
    }
}
import io.micronaut.context.annotation.Requires
import io.micronaut.http.HttpResponse
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Consumes
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Post

@Controller("/consumes")
class ConsumesController {

    @Post <b>(1)</b>
    HttpResponse index() {
        HttpResponse.ok()
    }

    @Consumes([MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON]) <b>(2)</b>
    @Post("/multiple")
    HttpResponse multipleConsumes() {
        HttpResponse.ok()
    }

    @Post(value = "/member", consumes = MediaType.TEXT_PLAIN) <b>(3)</b>
    HttpResponse consumesMember() {
        HttpResponse.ok()
    }
}
import io.micronaut.context.annotation.Requires
import io.micronaut.http.HttpResponse
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Consumes
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Post

@Controller("/consumes")
class ConsumesController {

    @Post <b>(1)</b>
    fun index(): HttpResponse<*> {
        return HttpResponse.ok<Any>()
    }

    @Consumes(MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON) <b>(2)</b>
    @Post("/multiple")
    fun multipleConsumes(): HttpResponse<*> {
        return HttpResponse.ok<Any>()
    }

    @Post(value = "/member", consumes = [MediaType.TEXT_PLAIN]) <b>(3)</b>
    fun consumesMember(): HttpResponse<*> {
        return HttpResponse.ok<Any>()
    }
}
1 By default, a controller’s action consumes request with Content-Type of type application/json .
2 @Consumes annotation takes a String[] of supported media types for an incoming request.
3 The consumes can also be specified with the consumes member of the method annotation.

Customizing Processed Content Types

Normally the JSON parsing only happens if the content type is application/json . The otherMediaTypeCodec classes behave in a similar manner that they have pre-defined content types they can process. To extend the list of media types that a given codec should process, you can provide configuration that will be stored inCodecConfiguration:

micronaut:
    codec:
        json:
            additionalTypes:
              - text/javascript
              - ...

Currently supported configuration prefixes are json , json-stream , text , and text-stream .

6.11 Reactive HTTP Request Processing

As mentioned previously, Micronaut is built on Netty which is designed around an Event loop model and non-blocking I/O. Micronaut will execute code defined in@Controller beans in the same thread as the request thread (an Event Loop thread).

This makes it critical that if you do any blocking I/O operations (for example interactions with Hibernate/JPA or JDBC) that you offload those tasks to a separate thread pool that does not block the Event loop.

For example the following configuration will configure the I/O thread pool as a fixed thread pool with 75 threads (similar to what a traditional blocking server such as Tomcat uses in the thread per connection model):

Configuring the IO thread pool

micronaut:
    executors:
        io:
            type: fixed
            nThreads: 75

To use this thread pool in a@Controller bean you have a number of options. The simplest option is to use the@ExecuteOn annotation which can be declared at the type or method level to indicate which configured thread pool you wish to run the method or methods of the controller on:

Using @ExecuteOn

import io.micronaut.http.annotation.*;
import io.micronaut.scheduling.TaskExecutors;
import io.micronaut.scheduling.annotation.ExecuteOn;

@Controller("/executeOn/people")
public class PersonController {

    private final PersonService personService;

    PersonController(
            PersonService personService) {
        this.personService = personService;
    }

    @Get("/{name}")
    @ExecuteOn(TaskExecutors.IO) <b>(1)</b>
    Person byName(String name) {
        return personService.findByName(name);
    }
}

Using @ExecuteOn

import io.micronaut.http.annotation.*
import io.micronaut.scheduling.TaskExecutors
import io.micronaut.scheduling.annotation.ExecuteOn

@Controller("/executeOn/people")
class PersonController {

    private final PersonService personService

    PersonController(
            PersonService personService) {
        this.personService = personService
    }

    @Get("/{name}")
    @ExecuteOn(TaskExecutors.IO) <b>(1)</b>
    Person byName(String name) {
        return personService.findByName(name)
    }
}

Using @ExecuteOn

import io.micronaut.http.annotation.*
import io.micronaut.scheduling.TaskExecutors
import io.micronaut.scheduling.annotation.ExecuteOn

@Controller("/executeOn/people")
class PersonController (private val personService: PersonService) {
    @Get("/{name}")
    @ExecuteOn(TaskExecutors.IO) <b>(1)</b>
    fun byName(name: String): Person {
        return personService.findByName(name)
    }
}
1 The@ExecuteOn annotation is used to execute the operation on the I/O thread pool

The value of the@ExecuteOn annotation can be any named executor defined under micronaut.executors .

Generally speaking for database operations you will want a thread pool configured that matches maximum number of connections you have specified in the database connection pool.

An alternative to the@ExecuteOn annotation is to use the facility provided by the reactive library you have chosen. RxJava for example features a subscribeOn method which allows you to alter which thread executes user code. For example:

RxJava subscribeOn Example

import io.micronaut.http.annotation.*;
import io.micronaut.scheduling.TaskExecutors;
import io.reactivex.*;
import io.reactivex.schedulers.Schedulers;
import javax.inject.Named;
import java.util.concurrent.ExecutorService;

@Controller("/subscribeOn/people")
public class PersonController {

    private final Scheduler scheduler;
    private final PersonService personService;

    PersonController(
            @Named(TaskExecutors.IO) ExecutorService executorService, <b>(1)</b>
            PersonService personService) {
        this.scheduler = Schedulers.from(executorService);
        this.personService = personService;
    }

    @Get("/{name}")
    Single<Person> byName(String name) {
        return Single.fromCallable(() ->
                personService.findByName(name) <b>(2)</b>
        ).subscribeOn(scheduler); <b>(3)</b>
    }
}

RxJava subscribeOn Example

import io.micronaut.http.annotation.*
import io.micronaut.scheduling.TaskExecutors
import io.reactivex.*
import io.reactivex.schedulers.Schedulers
import javax.inject.Named
import java.util.concurrent.ExecutorService

@Controller("/subscribeOn/people")
class PersonController {

    private final Scheduler scheduler
    private final PersonService personService

    PersonController(
            @Named(TaskExecutors.IO) ExecutorService executorService, <b>(1)</b>
            PersonService personService) {
        this.scheduler = Schedulers.from(executorService)
        this.personService = personService
    }

    @Get("/{name}")
    Single<Person> byName(String name) {
        return Single.fromCallable({ -> <b>(2)</b>
            personService.findByName(name)
        }).subscribeOn(scheduler) <b>(3)</b>
    }
}

RxJava subscribeOn Example

import io.micronaut.http.annotation.*
import io.micronaut.scheduling.TaskExecutors
import io.reactivex.*
import io.reactivex.schedulers.Schedulers
import java.util.concurrent.ExecutorService
import javax.inject.Named

@Controller("/subscribeOn/people")
class PersonController internal constructor(
        @Named(TaskExecutors.IO) executorService: ExecutorService, <b>(1)</b>
        val personService: PersonService) {
    private val scheduler: Scheduler

    init {
        scheduler = Schedulers.from(executorService)
    }

    @Get("/{name}")
    fun byName(name: String): Single<Person> {
        return Single.fromCallable { personService.findByName(name) } <b>(2)</b>
                .subscribeOn(scheduler) <b>(3)</b>
    }
}
1 The configured I/O executor service is injected
2 RxJava’s fromCallable method is used to wrap the blocking operation
3 RxJava’s subscribeOn method is used to schedule the operation on the I/O thread pool

6.11.1 Using the @Body Annotation

To parse the request body, you first need to indicate to Micronaut the parameter which will receive the data. This is done with theBody annotation.

The following example implements a simple echo server that echos the body sent in the request:

Using the @Body annotation

import io.micronaut.http.HttpResponse;
import io.micronaut.http.MediaType;
import io.micronaut.http.MutableHttpResponse;
import io.micronaut.http.annotation.Body;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Post;
import io.reactivex.Flowable;
import io.reactivex.Single;

import javax.validation.constraints.Size;

    @Post(value = "/echo", consumes = MediaType.TEXT_PLAIN) <b>(1)</b>
    String echo(@Size(max = 1024) @Body String text) { <b>(2)</b>
        return text; <b>(3)</b>
    }

Using the @Body annotation

import io.micronaut.http.MediaType
import io.micronaut.http.MutableHttpResponse
import io.micronaut.http.annotation.Body
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Post
import io.reactivex.Flowable
import io.reactivex.Single

import javax.validation.constraints.Size

    @Post(value = "/echo", consumes = MediaType.TEXT_PLAIN) <b>(1)</b>
    String echo(@Size(max = 1024) @Body String text) { <b>(2)</b>
        text <b>(3)</b>
    }

Using the @Body annotation

import io.micronaut.http.HttpResponse
import io.micronaut.http.MediaType
import io.micronaut.http.MutableHttpResponse
import io.micronaut.http.annotation.Body
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Post
import io.reactivex.Flowable
import io.reactivex.Single

import javax.validation.constraints.Size


@Controller("/receive")
open class MessageController {

    @Post(value = "/echo", consumes = [MediaType.TEXT_PLAIN]) <b>(1)</b>
    open fun echo(@Size(max = 1024) @Body text: String): String { <b>(2)</b>
        return text <b>(3)</b>
    }
}
1 ThePost annotation is used with aMediaType of text/plain (the default is application/json ).
2 TheBody annotation is used with a javax.validation.constraints.Size that limits the size of the body to at most 1MB. This constraint does not limit the amount of data read/buffered by the server.
3 The body is returned as the result of the method

Note that reading the request body is done in a non-blocking manner in that the request contents are read as the data becomes available and accumulated into the String passed to the method.

The micronaut.server.maxRequestSize setting in application.yml will limit the size of the data (the default maximum request size is 10MB) read/buffered by the server. @Size is not a replacement for this setting.

Regardless of the limit, for a large amount of data accumulating the data into a String in-memory may lead to memory strain on the server. A better approach is to include a Reactive library in your project (such as RxJava 2.x , Reactor or Akka ) that supports the Reactive streams implementation and stream the data it becomes available:

Using RxJava 2 to Read the request body

@Post(value = "/echo-flow", consumes = MediaType.TEXT_PLAIN) <b>(1)</b>
    Single<MutableHttpResponse<String>> echoFlow(@Body Flowable<String> text) { <b>(2)</b>
        return text.collect(StringBuffer::new, StringBuffer::append) <b>(3)</b>
                   .map(buffer ->
                        HttpResponse.ok(buffer.toString())
                   );
    }

Using RxJava 2 to Read the request body

@Post(value = "/echo-flow", consumes = MediaType.TEXT_PLAIN) <b>(1)</b>
    Single<MutableHttpResponse<String>> echoFlow(@Body Flowable<String> text) { <b>(2)</b>
        return text.collect({ x -> new StringBuffer()}, { StringBuffer sb, String s -> sb.append(s)}) <b>(3)</b>
                   .map({ buffer ->
                       HttpResponse.ok(buffer.toString())
                   });
    }

Using RxJava 2 to Read the request body

@Post(value = "/echo-flow", consumes = [MediaType.TEXT_PLAIN]) <b>(1)</b>
    open fun echoFlow(@Body text: Flowable<String>): Single<MutableHttpResponse<String>> { <b>(2)</b>
        return text.collect({ StringBuffer() }, { obj, str -> obj.append(str) }) <b>(3)</b>
                .map { buffer -> HttpResponse.ok(buffer.toString()) }
    }
1 In this case the method is altered to receive and return an RxJava 2.x Flowable type
2 A Single is returned so that Micronaut will only emit the response once the operation completes without blocking.
3 The collect method is used to accumulate the data in this simulated example, but it could for example write the data to logging service, database or whatever chunk by chunk
Body arguments of types that do not require conversion will cause Micronaut to skip decoding of the request!

6.11.2 Reactive Responses

The previous section introduced the notion of Reactive programming using RxJava 2.x and Micronaut.

Micronaut supports returning common reactive types such as Single or Observable (or the Mono type from Reactor 3.x), an instance of Publisher or CompletableFuture from any controller method.

The argument that is designated the body of the request using theBody annotation can also be a reactive type or a CompletableFuture .

When returning a reactive type Micronaut will subscribe to the returned reactive type on the same thread as the request (A Netty Event Loop thread). It is therefore important that if you perform any blocking operations you offload those operations to an appropriately configured thread pool for example, using RxJava’s subscribeOn(..) facility or@ExecuteOn.

See the section onConfiguring Thread Poolsfor information on the thread pools that Micronaut sets up and how to configure them.

To summarize, the following table illustrates some common response types and their handling:

Table 1. Micronaut Response Types
Type Description Example Signature

Publisher

Any type that implements the Publisher interface

Flowable<String> hello()

CompletableFuture

A Java CompletableFuture instance

CompletableFuture<String> hello()

HttpResponse

AnHttpResponse and optional response body

HttpResponse<Flowable<String>> hello()

CharSequence

Any implementation of CharSequence

String hello()

T

Any simple POJO type

Book show()

When returning a Reactive type, the type of reactive type has an impact on the response returned. For example, when returning a Flowable , Micronaut can not know the size of the response, so Transfer-Encoding type of Chunked is used. Whilst for types that emit a single result such as Single the Content-Length header will be populated.

6.12 JSON Binding with Jackson

The most common data interchange format nowadays is JSON.

In fact, the defaults in theController annotation specify that the controllers in Micronaut consume and produce JSON by default.

In order to do so in a non-blocking manner Micronaut builds on the Jackson Asynchronous JSON parsing API and Netty such that the reading of incoming JSON is done in a non-blocking manner.

Binding using Reactive Frameworks

From a developer perspective however, you can generally just work with Plain Old Java Objects (POJOs) and can optionally use a Reactive framework such as RxJava or Reactor. The following is an example of a controller that reads and saves an incoming POJO in a non-blocking way from JSON:

Using RxJava 2 to Read the JSON

@Controller("/people")
public class PersonController {

    Map<String, Person> inMemoryDatastore = new ConcurrentHashMap<>();

    @Post("/saveReactive")
    public Single<HttpResponse<Person>> save(@Body Single<Person> person) { <b>(1)</b>
        return person.map(p -> {
                    inMemoryDatastore.put(p.getFirstName(), p); <b>(2)</b>
                    return HttpResponse.created(p); <b>(3)</b>
                }
        );
    }

}

Using RxJava 2 to Read the JSON

@Controller("/people")
class PersonController {

    ConcurrentHashMap<String, Person> inMemoryDatastore = [:]

    @Post("/saveReactive")
    Single<HttpResponse<Person>> save(@Body Single<Person> person) { <b>(1)</b>
        person.map({ p ->
            inMemoryDatastore.put(p.getFirstName(), p) <b>(2)</b>
            HttpResponse.created(p) <b>(3)</b>
        })
    }

}

Using RxJava 2 to Read the JSON

@Controller("/people")
class PersonController {

    internal var inMemoryDatastore: MutableMap<String, Person> = ConcurrentHashMap()

    @Post("/saveReactive")
    fun save(@Body person: Single<Person>): Single<HttpResponse<Person>> { <b>(1)</b>
        return person.map { p ->
            inMemoryDatastore[p.firstName] = p <b>(2)</b>
            HttpResponse.created(p) <b>(3)</b>
        }
    }

}
1 The method receives a RxJava Single which emits the POJO once the JSON has been read
2 The map method is used to store the instance in Map
3 AnHttpResponse is returned

Using CURL from the command line you can POST JSON to the /people URI for the server to receive it:

Using CURL to Post JSON

$ curl -X POST localhost:8080/people -d '{"firstName":"Fred","lastName":"Flintstone","age":45}'

Binding Using CompletableFuture

The same method as the previous example can also be written with the CompletableFuture API instead:

Using CompletableFuture to Read the JSON

@Controller("/people")
public class PersonController {

    Map<String, Person> inMemoryDatastore = new ConcurrentHashMap<>();

    @Post("/saveFuture")
    public CompletableFuture<HttpResponse<Person>> save(@Body CompletableFuture<Person> person) {
        return person.thenApply(p -> {
                    inMemoryDatastore.put(p.getFirstName(), p);
                    return HttpResponse.created(p);
                }
        );
    }

}

Using CompletableFuture to Read the JSON

@Controller("/people")
class PersonController {

    ConcurrentHashMap<String, Person> inMemoryDatastore = [:]

    @Post("/saveFuture")
    CompletableFuture<HttpResponse<Person>> save(@Body CompletableFuture<Person> person) {
        person.thenApply({ p ->
            inMemoryDatastore.put(p.getFirstName(), p)
            HttpResponse.created(p)
        })
    }

}

Using CompletableFuture to Read the JSON

@Controller("/people")
class PersonController {

    internal var inMemoryDatastore: MutableMap<String, Person> = ConcurrentHashMap()

    @Post("/saveFuture")
    fun save(@Body person: CompletableFuture<Person>): CompletableFuture<HttpResponse<Person>> {
        return person.thenApply { p ->
            inMemoryDatastore[p.firstName] = p
            HttpResponse.created(p)
        }
    }

}

The above example uses the thenApply method to achieve the same as the previous example.

Binding using POJOs

Note however you can just as easily write:

Binding JSON POJOs

@Controller("/people")
public class PersonController {

    Map<String, Person> inMemoryDatastore = new ConcurrentHashMap<>();

    @Post
    public HttpResponse<Person> save(@Body Person person) {
        inMemoryDatastore.put(person.getFirstName(), person);
        return HttpResponse.created(person);
    }

}

Binding JSON POJOs

@Controller("/people")
class PersonController {

    ConcurrentHashMap<String, Person> inMemoryDatastore = [:]

    @Post
    HttpResponse<Person> save(@Body Person person) {
        inMemoryDatastore.put(person.getFirstName(), person)
        HttpResponse.created(person)
    }

}

Binding JSON POJOs

@Controller("/people")
class PersonController {

    internal var inMemoryDatastore: MutableMap<String, Person> = ConcurrentHashMap()

    @Post
    fun save(@Body person: Person): HttpResponse<Person> {
        inMemoryDatastore[person.firstName] = person
        return HttpResponse.created(person)
    }

}

Micronaut will only execute your method once the data has been read in a non-blocking manner.

The output produced by Jackson can be customized in a variety of manners, from defining Jackson modules to using Jackson’s annotations

Jackson Configuration

The Jackson ObjectMapper can be configured through normal configuration with theJacksonConfiguration class.

All jackson configuration keys start with jackson .

dateFormat

String

The date format

locale

String

Uses Locale.forLanguageTag . Example: en-US

timeZone

String

Uses TimeZone.getTimeZone . Example: PST

serializationInclusion

String

One of JsonInclude.Include . Example: ALWAYS

propertyNamingStrategy

String

Name of an instance of PropertyNamingStrategy . Example: SNAKE_CASE

defaultTyping

String

The global defaultTyping for polymorphic type handling from enum ObjectMapper.DefaultTyping . Example: NON_FINAL

Example:

jackson:
    serializationInclusion: ALWAYS

Features

All features can be configured with their name as the key and a boolean to indicate enabled or disabled.

serialization

Map

SerializationFeature

deserialization

Map

DeserializationFeature

mapper

Map

MapperFeature

parser

Map

JsonParser.Feature

generator

Map

JsonGenerator.Feature

Example:

jackson:
    serialization:
        indentOutput: true
        writeDatesAsTimestamps: false
    deserialization:
        useBigIntegerForInts: true
        failOnUnknownProperties: false

Support for @JsonView

You can use the @JsonView annotation to controller methods if you set jackson.json-view.enabled to true in application.yml .

Jackson’s @JsonView annotation allows you to control which properties are exposes on a per response basis. See Jackson JSON Views for more information.

Beans

In addition to configuration, beans can be registered to customize Jackson. All beans that extend any of the following classes will be registered with the object mapper.

Service Loader

Any modules registered via the service loader will also be added to the default object mapper.

6.13 Data Validation

It is easy to validate incoming data with Micronaut’s controllers with the.

First, add the Hibernate Validator configuration to your application:

implementation("io.micronaut.beanvalidation:micronaut-hibernate-validator")
<dependency>
    <groupId>io.micronaut.beanvalidation</groupId>
    <artifactId>micronaut-hibernate-validator</artifactId>
</dependency>

We can validate parameters using javax.validation annotations and theValidated annotation at the class level.

Example

import io.micronaut.context.annotation.Requires;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.validation.Validated;

import javax.validation.constraints.NotBlank;
import java.util.Collections;

@Validated <b>(1)</b>
@Controller("/email")
public class EmailController {

    @Get("/send")
    public HttpResponse send(@NotBlank String recipient, <b>(2)</b>
                             @NotBlank String subject) { <b>(2)</b>
        return HttpResponse.ok(Collections.singletonMap("msg", "OK"));
    }
}

Example

import io.micronaut.context.annotation.Requires;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.validation.Validated;

import javax.validation.constraints.NotBlank;
import java.util.Collections;

@Validated <b>(1)</b>
@Controller("/email")
class EmailController {

    @Get("/send")
    HttpResponse send(@NotBlank String recipient, <b>(2)</b>
                             @NotBlank String subject) { <b>(2)</b>
        HttpResponse.ok(Collections.singletonMap("msg", "OK"))
    }
}

Example

import io.micronaut.context.annotation.Requires
import io.micronaut.http.HttpResponse
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.validation.Validated

import javax.validation.constraints.NotBlank
import java.util.Collections


@Validated <b>(1)</b>
@Controller("/email")
open class EmailController {

    @Get("/send")
    open fun send(@NotBlank recipient: String, <b>(2)</b>
             @NotBlank subject: String): HttpResponse<*> { <b>(2)</b>
        return HttpResponse.ok(Collections.singletonMap("msg", "OK"))
    }
}
1 Annotate controller withValidated
2 subject and recipient cannot be blank.

If a validation error occurs a javax.validation.ConstraintViolationException will be thrown. By default, the integrated io.micronaut.validation.exception.ConstraintExceptionHandler is used to handle the exception leading to a behaviour as shown in the following test:

Example Test

@Test
    public void testParametersAreValidated() {
        HttpClientResponseException e = Assertions.assertThrows(HttpClientResponseException.class, () ->
            client.toBlocking().exchange("/email/send?subject=Hi&recipient="));
        HttpResponse response = e.getResponse();

        assertEquals(HttpStatus.BAD_REQUEST, response.getStatus());

        response = client.toBlocking().exchange("/email/send?subject=Hi&recipient=me@micronaut.example");

        assertEquals(HttpStatus.OK, response.getStatus());
    }

Example Test

def "test parameter validation"() {
        when:
        client.toBlocking().exchange('/email/send?subject=Hi&recipient=')

        then:
        def e = thrown(HttpClientResponseException)
        def response = e.response
        response.status == HttpStatus.BAD_REQUEST

        when:
        response = client.toBlocking().exchange('/email/send?subject=Hi&recipient=me@micronaut.example')

        then:
        response.status == HttpStatus.OK
    }

Example Test

"test params are validated"() {
            val e = shouldThrow<HttpClientResponseException> {
                client.toBlocking().exchange<Any>("/email/send?subject=Hi&recipient=")
            }
            var response = e.response

            response.status shouldBe HttpStatus.BAD_REQUEST

            response = client.toBlocking().exchange<Any>("/email/send?subject=Hi&recipient=me@micronaut.example")

            response.status shouldBe HttpStatus.OK
        }

If you want your own ExceptionHandler to handle the constraint exceptions, you need to annotate it with @Replaces(ConstraintExceptionHandler.class)

Often, you may want to use POJOs as controller method parameters.

/*
 * Copyright 2017-2020 original authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.micronaut.docs.datavalidation.pogo;

import io.micronaut.core.annotation.Introspected;

import javax.validation.constraints.NotBlank;

@Introspected
public class Email {

    @NotBlank <b>(1)</b>
    String subject;

    @NotBlank <b>(1)</b>
    String recipient;

    public String getSubject() {
        return subject;
    }

    public void setSubject(String subject) {
        this.subject = subject;
    }

    public String getRecipient() {
        return recipient;
    }

    public void setRecipient(String recipient) {
        this.recipient = recipient;
    }
}
/*
 * Copyright 2017-2020 original authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.micronaut.docs.datavalidation.pogo;

import io.micronaut.core.annotation.Introspected;

import javax.validation.constraints.NotBlank;

@Introspected
class Email {

    @NotBlank <b>(1)</b>
    String subject;

    @NotBlank <b>(1)</b>
    String recipient;
}
/*
 * Copyright 2017-2020 original authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.micronaut.docs.datavalidation.pogo

import io.micronaut.core.annotation.Introspected

import javax.validation.constraints.NotBlank

@Introspected
open class Email {

    @NotBlank <b>(1)</b>
    var subject: String? = null

    @NotBlank <b>(1)</b>
    var recipient: String? = null
}
1 You can use javax.validation annotations in your POJOs.

You need to annotate your controller withValidated. Also, you need to annotate the binding POJO with @Valid .

Example

import io.micronaut.context.annotation.Requires;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Body;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Post;
import io.micronaut.validation.Validated;

import javax.validation.Valid;
import java.util.Collections;

@Validated <b>(1)</b>
@Controller("/email")
public class EmailController {

    @Post("/send")
    public HttpResponse send(@Body @Valid Email email) { <b>(2)</b>
        return HttpResponse.ok(Collections.singletonMap("msg", "OK"));    }
}

Example

import io.micronaut.context.annotation.Requires;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Body;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Post;
import io.micronaut.validation.Validated;

import javax.validation.Valid;
import java.util.Collections;

@Validated <b>(1)</b>
@Controller("/email")
class EmailController {

    @Post("/send")
    HttpResponse send(@Body @Valid Email email) { <b>(2)</b>
        HttpResponse.ok(Collections.singletonMap("msg", "OK"))
    }
}

Example

import io.micronaut.context.annotation.Requires
import io.micronaut.http.HttpResponse
import io.micronaut.http.annotation.Body
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Post
import io.micronaut.validation.Validated

import javax.validation.Valid
import java.util.Collections


@Validated <b>(1)</b>
@Controller("/email")
open class EmailController {

    @Post("/send")
    open fun send(@Body @Valid email: Email): HttpResponse<*> { <b>(2)</b>
        return HttpResponse.ok(Collections.singletonMap("msg", "OK"))
    }
}
1 Annotate controller withValidated
2 Annotate the POJO which you wish to validate with @Valid

The validation of POJOs is shown in the following test:

public void testPoJoValidation() {
        HttpClientResponseException e = Assertions.assertThrows(HttpClientResponseException.class, () -> {
            Email email = new Email();
            email.subject = "Hi";
            email.recipient = "";
            client.toBlocking().exchange(HttpRequest.POST("/email/send", email));
        });
        HttpResponse response = e.getResponse();

        assertEquals(HttpStatus.BAD_REQUEST, response.getStatus());

        Email email = new Email();
        email.subject = "Hi";
        email.recipient = "me@micronaut.example";
        response = client.toBlocking().exchange(HttpRequest.POST("/email/send", email));

        assertEquals(HttpStatus.OK, response.getStatus());
    }
def "invoking /email/send parse parameters in a POJO and validates"() {
        when:
        Email email = new Email()
        email.subject = 'Hi'
        email.recipient = ''
        client.toBlocking().exchange(HttpRequest.POST('/email/send', email))

        then:
        def e = thrown(HttpClientResponseException)
        def response = e.response
        response.status == HttpStatus.BAD_REQUEST

        when:
        email = new Email()
        email.subject = 'Hi'
        email.recipient = 'me@micronaut.example'
        response = client.toBlocking().exchange(HttpRequest.POST('/email/send', email))

        then:
        response.status == HttpStatus.OK
    }
"test poko validation"() {
            val e = shouldThrow<HttpClientResponseException> {
                val email = Email()
                email.subject = "Hi"
                email.recipient = ""
                client.toBlocking().exchange<Email, Any>(HttpRequest.POST("/email/send", email))
            }
            var response = e.response

            response.status shouldBe HttpStatus.BAD_REQUEST

            val email = Email()
            email.subject = "Hi"
            email.recipient = "me@micronaut.example"
            response = client.toBlocking().exchange<Email, Any>(HttpRequest.POST("/email/send", email))

            response.status shouldBe HttpStatus.OK
        }
Bean injection is supported in custom constraints with the hibernate validator configuration.

6.14 Serving Static Resources

Static resource resolution is disabled by default. Micronaut supports resolving resources from the classpath or the file system.

See the information below for available configuration options:

Table 1. Configuration Properties for StaticResourceConfiguration
Property Type Description

micronaut.router.static-resources.*.enabled

boolean

Sets whether this specific mapping is enabled. Default value (true).

micronaut.router.static-resources.*.paths

java.util.List

A list of paths either starting with classpath: or file: . You can serve files from anywhere on disk or the classpath. For example to serve static resources from src/main/resources/public , you would use classpath:public as the path.

micronaut.router.static-resources.*.mapping

java.lang.String

The path resources should be served from. Uses ant path matching. Default value ("/**").

6.15 Error Handling

Sometimes with distributed applications, bad things happen. Having a good way to handle errors is important.

Status Handlers

TheError annotation supports defining either an exception class or an HTTP status. Methods decorated with the annotation will be invoked as the result of other controller methods. The annotation also supports the notion of global and local, local being the default.

Local error handlers will only respond to methods defined in the same controller. Global error handlers can respond to any method in any controller. A local error handler is always searched for first when resolving which handler to execute.

When defining an error handler for an exception, you can specify the exception instance as an argument to the method and omit the exception property of the annotation.

Local Error Handling

For example the following method will handling JSON parse exceptions from Jackson for the scope of the declaring controller:

Local exception handler

@Error
    public HttpResponse<JsonError> jsonError(HttpRequest request, JsonParseException jsonParseException) { <b>(1)</b>
        JsonError error = new JsonError("Invalid JSON: " + jsonParseException.getMessage()) <b>(2)</b>
                .link(Link.SELF, Link.of(request.getUri()));

        return HttpResponse.<JsonError>status(HttpStatus.BAD_REQUEST, "Fix Your JSON")
                .body(error); <b>(3)</b>
    }

Local exception handler

@Error
    HttpResponse<JsonError> jsonError(HttpRequest request, JsonParseException jsonParseException) { <b>(1)</b>
        JsonError error = new JsonError("Invalid JSON: " + jsonParseException.getMessage()) <b>(2)</b>
                .link(Link.SELF, Link.of(request.getUri()))

        HttpResponse.<JsonError>status(HttpStatus.BAD_REQUEST, "Fix Your JSON")
                .body(error) <b>(3)</b>
    }

Local exception handler

@Error
    fun jsonError(request: HttpRequest<*>, jsonParseException: JsonParseException): HttpResponse<JsonError> { <b>(1)</b>
        val error = JsonError("Invalid JSON: " + jsonParseException.message) <b>(2)</b>
                .link(Link.SELF, Link.of(request.uri))

        return HttpResponse.status<JsonError>(HttpStatus.BAD_REQUEST, "Fix Your JSON")
                .body(error) <b>(3)</b>
    }
1 A method that explicitly handles JsonParseException is declared
2 An instance ofJsonError is returned.
3 A custom response is returned to handle the error

Local status handler

@Error(status = HttpStatus.NOT_FOUND)
    public HttpResponse<JsonError> notFound(HttpRequest request) { <b>(1)</b>
        JsonError error = new JsonError("Person Not Found") <b>(2)</b>
                .link(Link.SELF, Link.of(request.getUri()));

        return HttpResponse.<JsonError>notFound()
                .body(error); <b>(3)</b>
    }

Local status handler

@Error(status = HttpStatus.NOT_FOUND)
    HttpResponse<JsonError> notFound(HttpRequest request) { <b>(1)</b>
        JsonError error = new JsonError("Person Not Found") <b>(2)</b>
                .link(Link.SELF, Link.of(request.getUri()))

        HttpResponse.<JsonError>notFound()
                .body(error) <b>(3)</b>
    }

Local status handler

@Error(status = HttpStatus.NOT_FOUND)
    fun notFound(request: HttpRequest<*>): HttpResponse<JsonError> { <b>(1)</b>
        val error = JsonError("Person Not Found") <b>(2)</b>
                .link(Link.SELF, Link.of(request.uri))

        return HttpResponse.notFound<JsonError>()
                .body(error) <b>(3)</b>
    }
1 TheError declares whichHttpStatus error code to handle (in this case 404)
2 AJsonError instance is returned for all 404 responses
3 AnNOT_FOUND response is returned

Global Error Handling

Global error handler

@Error(global = true) <b>(1)</b>
    public HttpResponse<JsonError> error(HttpRequest request, Throwable e) {
        JsonError error = new JsonError("Bad Things Happened: " + e.getMessage()) <b>(2)</b>
                .link(Link.SELF, Link.of(request.getUri()));

        return HttpResponse.<JsonError>serverError()
                .body(error); <b>(3)</b>
    }

Global error handler

@Error(global = true) <b>(1)</b>
    HttpResponse<JsonError> error(HttpRequest request, Throwable e) {
        JsonError error = new JsonError("Bad Things Happened: " + e.getMessage()) <b>(2)</b>
                .link(Link.SELF, Link.of(request.getUri()))

        HttpResponse.<JsonError>serverError()
                .body(error) <b>(3)</b>
    }

Global error handler

@Error(global = true) <b>(1)</b>
    fun error(request: HttpRequest<*>, e: Throwable): HttpResponse<JsonError> {
        val error = JsonError("Bad Things Happened: " + e.message) <b>(2)</b>
                .link(Link.SELF, Link.of(request.uri))

        return HttpResponse.serverError<JsonError>()
                .body(error) <b>(3)</b>
    }
1 TheError is used to declare the method a global error handler
2 AJsonError instance is returned for all errors
3 An INTERNAL_SERVER_ERROR response is returned

Global status handler

@Error(status = HttpStatus.NOT_FOUND)
    public HttpResponse<JsonError> notFound(HttpRequest request) { <b>(1)</b>
        JsonError error = new JsonError("Person Not Found") <b>(2)</b>
                .link(Link.SELF, Link.of(request.getUri()));

        return HttpResponse.<JsonError>notFound()
                .body(error); <b>(3)</b>
    }

Global status handler

@Error(status = HttpStatus.NOT_FOUND)
    HttpResponse<JsonError> notFound(HttpRequest request) { <b>(1)</b>
        JsonError error = new JsonError("Person Not Found") <b>(2)</b>
                .link(Link.SELF, Link.of(request.getUri()))

        HttpResponse.<JsonError>notFound()
                .body(error) <b>(3)</b>
    }

Global status handler

@Error(status = HttpStatus.NOT_FOUND)
    fun notFound(request: HttpRequest<*>): HttpResponse<JsonError> { <b>(1)</b>
        val error = JsonError("Person Not Found") <b>(2)</b>
                .link(Link.SELF, Link.of(request.uri))

        return HttpResponse.notFound<JsonError>()
                .body(error) <b>(3)</b>
    }
1 TheError declares whichHttpStatus error code to handle (in this case 404)
2 AJsonError instance is returned for all 404 responses
3 AnNOT_FOUND response is returned
A few things to note about theError annotation. Two identical @Error annotations that are global cannot be declared. Two identical @Error annotations that are non-global cannot be declared in the same controller. If an @Error annotation with the same parameter exists as global and another as a local, the local one will take precedence.

ExceptionHandler

Additionally you can implement aExceptionHandler; a generic hook for handling exceptions that occurs during the execution of an HTTP request.

Imagine your e-commerce app throws an OutOfStockException when a book is out of stock:

public class OutOfStockException extends RuntimeException {
}
class OutOfStockException extends RuntimeException {
}
class OutOfStockException : RuntimeException()

Along with BookController :

@Controller("/books")
public class BookController {

    @Produces(MediaType.TEXT_PLAIN)
    @Get("/stock/{isbn}")
    Integer stock(String isbn) {
        throw new OutOfStockException();
    }
}
@Controller("/books")
class BookController {

    @Produces(MediaType.TEXT_PLAIN)
    @Get("/stock/{isbn}")
    Integer stock(String isbn) {
        throw new OutOfStockException()
    }
}
@Controller("/books")
class BookController {

    @Produces(MediaType.TEXT_PLAIN)
    @Get("/stock/{isbn}")
    internal fun stock(isbn: String): Int? {
        throw OutOfStockException()
    }
}

If you don’t handle the exception the server returns a 500 (Internal Server Error) status code.

If you want to respond 200 OK with 0 (stock level) as the response body when the OutOfStockException is thrown, you could register a ExceptionHandler :

@Produces
@Singleton
@Requires(classes = {OutOfStockException.class, ExceptionHandler.class})
public class OutOfStockExceptionHandler implements ExceptionHandler<OutOfStockException, HttpResponse> {

    @Override
    public HttpResponse handle(HttpRequest request, OutOfStockException exception) {
        return HttpResponse.ok(0);
    }
}
@Produces
@Singleton
@Requires(classes = [OutOfStockException.class, ExceptionHandler.class])
class OutOfStockExceptionHandler implements ExceptionHandler<OutOfStockException, HttpResponse> {

    @Override
    HttpResponse handle(HttpRequest request, OutOfStockException exception) {
        HttpResponse.ok(0)
    }
}
@Produces
@Singleton
@Requirements(
        Requires(classes = [OutOfStockException::class, ExceptionHandler::class])
)
class OutOfStockExceptionHandler : ExceptionHandler<OutOfStockException, HttpResponse<*>> {

    override fun handle(request: HttpRequest<*>, exception: OutOfStockException): HttpResponse<*> {
        return HttpResponse.ok(0)
    }
}
An @Error annotation capturing an exception has precedence over an implementation of ExceptionHandler capturing the same exception.

6.16 API Versioning

Since 1.1.x, Micronaut supports API versioning via a dedicated@Version annotation.

The following example demonstrates how to version an API:

Versioning an API

import io.micronaut.core.version.annotation.Version;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;

@Controller("/versioned")
class VersionedController {

    @Version("1") <b>(1)</b>
    @Get("/hello")
    String helloV1() {
        return "helloV1";
    }

    @Version("2") <b>(2)</b>
    @Get("/hello")
    String helloV2() {
        return "helloV2";
    }

Versioning an API

import io.micronaut.core.version.annotation.Version
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get

@Controller("/versioned")
class VersionedController {

    @Version("1") <b>(1)</b>
    @Get("/hello")
    String helloV1() {
        "helloV1"
    }

    @Version("2") <b>(2)</b>
    @Get("/hello")
    String helloV2() {
        "helloV2"
    }

Versioning an API

import io.micronaut.core.version.annotation.Version
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get


@Controller("/versioned")
internal class VersionedController {

    @Version("1") <b>(1)</b>
    @Get("/hello")
    fun helloV1(): String {
        return "helloV1"
    }

    @Version("2") <b>(2)</b>
    @Get("/hello")
    fun helloV2(): String {
        return "helloV2"
    }
1 The helloV1 method is declared as version 1
2 The helloV2 method is declared as version 2

You should then enabling versioning by setting micronaut.router.versioning.enabled to true in application.yml :

Enabling Versioning

micronaut:
    router:
        versioning:
            enabled: true

By default Micronaut has 2 out-of-the-box strategies for resolving the version that are based on an HTTP header named X-API-VERSION or a request parameter named api-version , however this is configurable. A full configuration example can be seen below:

Configuring Versioning

micronaut:
    router:
        versioning:
            enabled: true <i data-value="1"></i><b>(1)</b>
            parameter:
                enabled: false <i data-value="2"></i><b>(2)</b>
                names: 'v,api-version' <i data-value="3"></i><b>(3)</b>
            header:
                enabled: true <i data-value="4"></i><b>(4)</b>
                names: <i data-value="5"></i><b>(5)</b>
                    - 'X-API-VERSION'
                    - 'Accept-Version'
1 Enables versioning
2 Enables or disables parameter based versioning
3 Specify the parameter names as a comma separated list
4 Enables or disables header based versioning
5 Specify the header names as a YAML list

If this is not enough you can also implement the RequestVersionResolver interface which receives theHttpRequest and can implement any strategy you choose.

Default Version

It is possible to supply a default version through configuration.

Configuring Default Version

micronaut:
    router:
        versioning:
            enabled: true
            default-version: 3 <i data-value="1"></i><b>(1)</b>
1 Sets the default version

A route will not be matched if the following conditions are met:

  • The default version is configured

  • No version is found in the request

  • The route defines a version

  • The route version does not match the default version

If the incoming request specifies a version then the default version has no effect.

Versioning Client Requests

Micronaut’sDeclarative HTTP clientalso supports automatic versioning of outgoing requests via the@Version annotation.

By default if you annotate a client interface with@Version the value supplied to the annotation will be included using the X-API-VERSION header.

For example:

import io.micronaut.core.version.annotation.Version;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.annotation.Client;
import io.reactivex.Single;

@Client("/hello")
@Version("1") <b>(1)</b>
public interface HelloClient {

    @Get("/greeting/{name}")
    String sayHello(String name);

    @Version("2")
    @Get("/greeting/{name}")
    Single<String> sayHelloTwo(String name); <b>(2)</b>
}
import io.micronaut.core.version.annotation.Version
import io.micronaut.http.annotation.Get
import io.micronaut.http.client.annotation.Client
import io.reactivex.Single


@Client("/hello")
@Version("1") <b>(1)</b>
interface HelloClient {

    @Get("/greeting/{name}")
    String sayHello(String name)

    @Version("2")
    @Get("/greeting/{name}")
    Single<String> sayHelloTwo(String name) <b>(2)</b>
}
import io.micronaut.core.version.annotation.Version
import io.micronaut.http.annotation.Get
import io.micronaut.http.client.annotation.Client
import io.reactivex.Single

@Client("/hello")
@Version("1") <b>(1)</b>
interface HelloClient {

    @Get("/greeting/{name}")
    fun sayHello(name : String) : String

    @Version("2")
    @Get("/greeting/{name}")
    fun sayHelloTwo(name : String) : Single<String>  <b>(2)</b>
}
1 The@Version can be used as the type level to specify the version to use for all methods
2 When defined at the method level it is used only for that method

The default behaviour for how the version is sent for each call can be configured with DefaultClientVersioningConfiguration :

Table 1. Configuration Properties for DefaultClientVersioningConfiguration
Property Type Description

micronaut.http.client.versioning.default.headers

java.util.List

The list of request header names.

micronaut.http.client.versioning.default.parameters

java.util.List

The list of request query parameter names.

For example to use Accept-Version as the header name:

Configuring Client Versioning

micronaut:
    http:
        client:
            versioning:
                default:
                    headers:
                        - 'Accept-Version'
                        - 'X-API-VERSION'

The default key is used to refer to the default configuration. You can specify client specific configuration by using the value passed to @Client (typically the service ID). For example:

Configuring Versioning

micronaut:
    http:
        client:
            versioning:
                greeting-service:
                    headers:
                        - 'Accept-Version'
                        - 'X-API-VERSION'

The above uses a key called greeting-service which can be used to configure a client annotated with @Client('greeting-service') .

6.17 Handling Form Data

In order to make data binding model customizations consistent between form data and JSON, Micronaut uses Jackson to implement binding data from form submissions.

The advantage of this approach is that the same Jackson annotations used for customizing JSON binding can be used for form submissions too.

What this means in practise is that in order to bind regular form data the only change required to the previous JSON binding code is updating theMediaType consumed:

Binding Form Data to POJOs

@Controller("/people")
public class PersonController {

    Map<String, Person> inMemoryDatastore = new ConcurrentHashMap<>();

    @Post
    public HttpResponse<Person> save(@Body Person person) {
        inMemoryDatastore.put(person.getFirstName(), person);
        return HttpResponse.created(person);
    }

}

Binding Form Data to POJOs

@Controller("/people")
class PersonController {

    ConcurrentHashMap<String, Person> inMemoryDatastore = [:]

    @Post
    HttpResponse<Person> save(@Body Person person) {
        inMemoryDatastore.put(person.getFirstName(), person)
        HttpResponse.created(person)
    }

}

Binding Form Data to POJOs

@Controller("/people")
class PersonController {

    internal var inMemoryDatastore: MutableMap<String, Person> = ConcurrentHashMap()

    @Post
    fun save(@Body person: Person): HttpResponse<Person> {
        inMemoryDatastore[person.firstName] = person
        return HttpResponse.created(person)
    }

}
To avoid denial of service attacks, collection types and arrays created during binding are limited by the setting jackson.arraySizeThreshold in application.yml

Alternatively, instead of using a POJO you can bind form data directly to method parameters (which works with JSON too!):

Binding Form Data to Parameters

@Controller("/people")
public class PersonController {

    Map<String, Person> inMemoryDatastore = new ConcurrentHashMap<>();

    @Post("/saveWithArgs")
    public HttpResponse<Person> save(String firstName, String lastName, Optional<Integer> age) {
        Person p = new Person(firstName, lastName);
        age.ifPresent(p::setAge);
        inMemoryDatastore.put(p.getFirstName(), p);
        return HttpResponse.created(p);
    }

}

Binding Form Data to Parameters

@Controller("/people")
class PersonController {

    ConcurrentHashMap<String, Person> inMemoryDatastore = [:]

    @Post("/saveWithArgs")
    HttpResponse<Person> save(String firstName, String lastName, Optional<Integer> age) {
        Person p = new Person(firstName, lastName)
        age.ifPresent({ a -> p.setAge(a)})
        inMemoryDatastore.put(p.getFirstName(), p)
        HttpResponse.created(p)
    }

}

Binding Form Data to Parameters

@Controller("/people")
class PersonController {

    internal var inMemoryDatastore: MutableMap<String, Person> = ConcurrentHashMap()

    @Post("/saveWithArgs")
    fun save(firstName: String, lastName: String, age: Optional<Int>): HttpResponse<Person> {
        val p = Person(firstName, lastName)
        age.ifPresent { p.age = it }
        inMemoryDatastore[p.firstName] = p
        return HttpResponse.created(p)
    }

}

As you can see from the example above, this approach allows you to use features such as support for Optional types and restrict the parameters to be bound (When using POJOs you must be careful to use Jackson annotations to exclude properties that should not be bound).

6.18 Writing Response Data

Reactively Writing Response Data

Micronaut’s HTTP server supports writing chunks of response data by returning a Publisher the emits objects that can be encoded to the HTTP response.

The following table summarizes example return type signatures and the behaviour the server exhibits to handle each of them:

Return Type Description

Flowable<byte[]>

A Flowable that emits each chunk of content as a byte[] without blocking

Flux<ByteBuf>

A Reactor Flux that emits each chunk as a Netty ByteBuf

Publisher<String>

A Publisher that emits each chunk of content as a String

Flowable<Book>

When emitting a POJO each emitted object is encoded as JSON by default without blocking

When returning reactive type the server will use a Transfer-Encoding of chunked and keep writing data until the Publisher 's onComplete method is called.

The server will request a single item from the Publisher , write the item and then request the next item, controlling back pressure.

It is up to the implementation of the Publisher to schedule any blocking I/O work that may be done as a result of subscribing to the publisher.

Performing Blocking I/O

In some cases you may wish to integrate with a library that does not support non-blocking I/O.

In this case you can return aWritable object from any controller method. TheWritable has various signatures that allowing writing to traditional blocking streams like Writer or OutputStream .

When returning aWritable object the blocking I/O operation will be shifted to the I/O thread pool so that the Netty event loop is not blocked.

See the section on configuringfor details on how to configure the I/O thread pool to meet the requirements of your application.

The following example demonstrates how to use this API with Groovy’s SimpleTemplateEngine to write a server side template:

Performing Blocking I/O

import groovy.text.SimpleTemplateEngine;
import groovy.text.Template;
import io.micronaut.core.io.Writable;
import io.micronaut.core.util.CollectionUtils;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.server.exceptions.HttpServerException;

@Controller("/template")
public class TemplateController {

    private final SimpleTemplateEngine templateEngine = new SimpleTemplateEngine();
    private final Template template;

    public TemplateController() {
        template = initTemplate(); <b>(1)</b>
    }

    @Get(value = "/welcome", produces = MediaType.TEXT_PLAIN)
    Writable render() { <b>(2)</b>
        return writer -> template.make( <b>(3)</b>
            CollectionUtils.mapOf(
                    "firstName", "Fred",
                    "lastName", "Flintstone"
            )
        ).writeTo(writer);
    }

    private Template initTemplate() {
        Template template;
        try {
            template = templateEngine.createTemplate(
                    "Dear $firstName $lastName. Nice to meet you."
            );
        } catch (Exception e) {
            throw new HttpServerException("Cannot create template");
        }
        return template;
    }
}

Performing Blocking I/O

import groovy.text.SimpleTemplateEngine;
import groovy.text.Template;
import io.micronaut.core.io.Writable;
import io.micronaut.core.util.CollectionUtils;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.server.exceptions.HttpServerException;

@Controller("/template")
class TemplateController {

    private final SimpleTemplateEngine templateEngine = new SimpleTemplateEngine()
    private final Template template

    TemplateController() {
        template = initTemplate() <b>(1)</b>
    }

    @Get(value = "/welcome", produces = MediaType.TEXT_PLAIN)
    Writable render() { <b>(2)</b>
        { writer ->
            template.make( <b>(3)</b>
                    CollectionUtils.mapOf(
                            "firstName", "Fred",
                            "lastName", "Flintstone"
                    )
            ).writeTo(writer)
        }
    }

    private Template initTemplate() {
        Template template
        try {
            template = templateEngine.createTemplate(
                    'Dear $firstName $lastName. Nice to meet you.'
            )
        } catch (Exception e) {
            throw new HttpServerException("Cannot create template")
        }
        template
    }
}

Performing Blocking I/O

import groovy.text.SimpleTemplateEngine
import groovy.text.Template
import io.micronaut.core.io.Writable
import io.micronaut.core.util.CollectionUtils
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.server.exceptions.HttpServerException
import java.io.Writer


@Controller("/template")
class TemplateController {

    private val templateEngine = SimpleTemplateEngine()
    private val template: Template

    init {
        template = initTemplate() <b>(1)</b>
    }

    @Get(value = "/welcome", produces = [MediaType.TEXT_PLAIN])
    internal fun render(): Writable { <b>(2)</b>
        return { writer: Writer ->
            val writable = template.make( <b>(3)</b>
                    CollectionUtils.mapOf(
                            "firstName", "Fred",
                            "lastName", "Flintstone"
                    )
            )
            writable.writeTo(writer)
        } as Writable
    }

    private fun initTemplate(): Template {
        val template: Template
        try {
            template = templateEngine.createTemplate(
                    "Dear \$firstName \$lastName. Nice to meet you."
            )
        } catch (e: Exception) {
            throw HttpServerException("Cannot create template")
        }

        return template
    }
}
1 The controller creates a simple template
2 The controller method returns aWritable
3 The returned function receives a Writer and calls writeTo on the template.

404 Responses

Often, you want to respond 404 (Not Found) when you don’t find an item in your persistence layer or in similar scenarios.

See the following example:

@Controller("/books")
public class BooksController {

    @Get("/stock/{isbn}")
    public Map stock(String isbn) {
        return null; <b>(1)</b>
    }

    @Get("/maybestock/{isbn}")
    public Maybe<Map> maybestock(String isbn) {
        return Maybe.empty(); <b>(2)</b>
    }
}
@Controller("/books")
class BooksController {

    @Get("/stock/{isbn}")
    Map stock(String isbn) {
        null <b>(1)</b>
    }

    @Get("/maybestock/{isbn}")
    Maybe<Map> maybestock(String isbn) {
        Maybe.empty() <b>(2)</b>
    }
}
@Controller("/books")
class BooksController {

    @Get("/stock/{isbn}")
    fun stock(isbn: String): Map<*, *>? {
        return null <b>(1)</b>
    }

    @Get("/maybestock/{isbn}")
    fun maybestock(isbn: String): Maybe<Map<*, *>> {
        return Maybe.empty() <b>(2)</b>
    }
}
1 Returning null triggers a 404 (Not Found) response.
2 Returning an empty Maybe triggers a 404 (Not Found) response.
Responding with an empty Publisher or Flowable will result in an empty array being returned if the content type is JSON.

6.19 File Uploads

Handling of file uploads has special treatment in Micronaut. Support is provided for streaming of uploads in a non-blocking manner through streaming uploads or completed uploads.

To receive data from a multipart request, set the consumes argument of the method annotation toMULTIPART_FORM_DATA. For example:

@Post(consumes = MediaType.MULTIPART_FORM_DATA)
HttpResponse upload( ... )

Route Arguments

How the files are received by your method is determined by the type of the arguments. Data can be received a chunk at a time or when an upload is completed.

If the route argument name can’t or shouldn’t match the name of the part in the request, simply add the@Part annotation to the argument and specify the name that is expected to be in the request.

Chunk Data Types

PartData is the data type used to represent a chunk of data received in a multipart request. There are methods on thePartData interface to convert the data to a byte[] , InputStream , or a ByteBuffer .

Data can only be retrieved from aPartData once. The underlying buffer will be released which causes further attempts to fail.

Route arguments of type Publisher<PartData> will be treated as only intended to receive a single file and each chunk of the received file will be sent downstream. If the generic type is something other thanPartData, conversion will be attempted using Micronaut’s conversion service. Conversions to String and byte[] are supported by default.

If requirements dictate you must have knowledge about the metadata of the file being received, a special class calledStreamingFileUpload has been created that is a Publisher<PartData> , but also has file information like the content type and file name.

Streaming file upload

import io.micronaut.http.HttpResponse;
import io.micronaut.http.HttpStatus;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Post;
import io.micronaut.http.multipart.StreamingFileUpload;
import io.reactivex.Single;
import java.io.File;
import org.reactivestreams.Publisher;
import java.io.IOException;

@Controller("/upload")
public class UploadController {

    @Post(value = "/", consumes = MediaType.MULTIPART_FORM_DATA, produces = MediaType.TEXT_PLAIN) <b>(1)</b>
    public Single<HttpResponse<String>> upload(StreamingFileUpload file) { <b>(2)</b>
        File tempFile;
        try {
            tempFile = File.createTempFile(file.getFilename(), "temp");
        } catch (IOException e) {
            return Single.error(e);
        }
        Publisher<Boolean> uploadPublisher = file.transferTo(tempFile); <b>(3)</b>
        return Single.fromPublisher(uploadPublisher)  <b>(4)</b>
            .map(success -> {
                if (success) {
                    return HttpResponse.ok("Uploaded");
                } else {
                    return HttpResponse.<String>status(HttpStatus.CONFLICT)
                                       .body("Upload Failed");
                }
            });
    }
}

Streaming file upload

import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Post
import io.micronaut.http.multipart.StreamingFileUpload
import io.reactivex.Single
import org.reactivestreams.Publisher

@Controller("/upload")
class UploadController {

    @Post(value = "/", consumes = MediaType.MULTIPART_FORM_DATA, produces = MediaType.TEXT_PLAIN) <b>(1)</b>
    Single<HttpResponse<String>> upload(StreamingFileUpload file) { <b>(2)</b>
        File tempFile = File.createTempFile(file.filename, "temp")
        Publisher<Boolean> uploadPublisher = file.transferTo(tempFile) <b>(3)</b>
        Single.fromPublisher(uploadPublisher)  <b>(4)</b>
            .map({ success ->
                if (success) {
                    HttpResponse.ok("Uploaded")
                } else {
                    HttpResponse.<String>status(HttpStatus.CONFLICT)
                            .body("Upload Failed")
                }
            })
    }

}

Streaming file upload

import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Post
import io.micronaut.http.multipart.StreamingFileUpload
import io.reactivex.Single
import java.io.File

@Controller("/upload")
class UploadController {

    @Post(value = "/", consumes = [MediaType.MULTIPART_FORM_DATA], produces = [MediaType.TEXT_PLAIN]) <b>(1)</b>
    fun upload(file: StreamingFileUpload): Single<HttpResponse<String>> { <b>(2)</b>
        val tempFile = File.createTempFile(file.filename, "temp")
        val uploadPublisher = file.transferTo(tempFile) <b>(3)</b>
        return Single.fromPublisher(uploadPublisher)  <b>(4)</b>
                .map { success ->
                    if (success) {
                        HttpResponse.ok("Uploaded")
                    } else {
                        HttpResponse.status<String>(HttpStatus.CONFLICT)
                                .body("Upload Failed")
                    }
                }
    }

}
1 The method is set to consumeMULTIPART_FORM_DATA
2 The method parameters match form attribute names. In this case the file will match for example an <input type="file" name="file">
3 The StreamingFileUpload.transferTo(java.lang.String) method is used to transfer the file to the server. The method returns a Publisher
4 The returned Single subscribes to the Publisher and outputs a response once the upload is complete, without blocking.

Whole Data Types

Route arguments that are not publishers will cause the route execution to be delayed until the upload has finished. The received data will attempt to be converted to the requested type. Conversions to a String or byte[] are supported by default. In addition, the file can be converted to a POJO if a media type codec has been registered that supports the media type of the file. A media type codec is included by default that allows conversion of JSON files to POJOs.

Receiving a byte array

import io.micronaut.http.HttpResponse;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Post;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

@Controller("/upload")
public class BytesUploadController {

    @Post(value = "/bytes",
            consumes = MediaType.MULTIPART_FORM_DATA,
            produces = MediaType.TEXT_PLAIN) <b>(1)</b>
    public HttpResponse<String> uploadBytes(byte[] file, String fileName) { <b>(2)</b>
        try {
            File tempFile = File.createTempFile(fileName, "temp");
            Path path = Paths.get(tempFile.getAbsolutePath());
            Files.write(path, file); <b>(3)</b>
            return HttpResponse.ok("Uploaded");
        } catch (IOException exception) {
            return HttpResponse.badRequest("Upload Failed");
        }
    }
}

Receiving a byte array

import io.micronaut.http.HttpResponse
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Post

import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths

@Controller("/upload")
class BytesUploadController {

    @Post(value = "/bytes",
            consumes = MediaType.MULTIPART_FORM_DATA,
            produces = MediaType.TEXT_PLAIN) <b>(1)</b>
    HttpResponse<String> uploadBytes(byte[] file, String fileName) { <b>(2)</b>
        try {
            File tempFile = File.createTempFile(fileName, "temp")
            Path path = Paths.get(tempFile.absolutePath)
            Files.write(path, file) <b>(3)</b>
            HttpResponse.ok("Uploaded")
        } catch (IOException exception) {
            HttpResponse.badRequest("Upload Failed")
        }
    }
}

Receiving a byte array

import io.micronaut.http.HttpResponse
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Post

import java.io.File
import java.io.IOException
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths

@Controller("/upload")
class BytesUploadController {

    @Post(value = "/bytes",
            consumes = [MediaType.MULTIPART_FORM_DATA],
            produces = [MediaType.TEXT_PLAIN]) <b>(1)</b>
    fun uploadBytes(file: ByteArray, fileName: String): HttpResponse<String> { <b>(2)</b>
        return try {
            val tempFile = File.createTempFile(fileName, "temp")
            val path = Paths.get(tempFile.absolutePath)
            Files.write(path, file) <b>(3)</b>
            HttpResponse.ok("Uploaded")
        } catch (exception: IOException) {
            HttpResponse.badRequest("Upload Failed")
        }

    }
}

If requirements dictate you must have knowledge about the metadata of the file being received, a special class calledCompletedFileUpload has been created that has methods to retrieve the data of the file, but also has file information like the content type and file name.

File upload with metadata

import io.micronaut.http.HttpResponse;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Post;
import io.micronaut.http.multipart.CompletedFileUpload;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

@Controller("/upload")
public class CompletedUploadController {

    @Post(value = "/completed",
            consumes = MediaType.MULTIPART_FORM_DATA,
            produces = MediaType.TEXT_PLAIN) <b>(1)</b>
    public HttpResponse<String> uploadCompleted(CompletedFileUpload file) { <b>(2)</b>
        try {
            File tempFile = File.createTempFile(file.getFilename(), "temp"); <b>(3)</b>
            Path path = Paths.get(tempFile.getAbsolutePath());
            Files.write(path, file.getBytes()); <b>(3)</b>
            return HttpResponse.ok("Uploaded");
        } catch (IOException exception) {
            return HttpResponse.badRequest("Upload Failed");
        }
    }
}

File upload with metadata

import io.micronaut.http.HttpResponse
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Post
import io.micronaut.http.multipart.CompletedFileUpload

import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths

@Controller("/upload")
class CompletedUploadController {

    @Post(value = "/completed",
            consumes = MediaType.MULTIPART_FORM_DATA,
            produces = MediaType.TEXT_PLAIN) <b>(1)</b>
    HttpResponse<String> uploadCompleted(CompletedFileUpload file) { <b>(2)</b>
        try {
            File tempFile = File.createTempFile(file.filename, "temp") <b>(3)</b>
            Path path = Paths.get(tempFile.absolutePath)
            Files.write(path, file.bytes) <b>(3)</b>
            HttpResponse.ok("Uploaded")
        } catch (IOException exception) {
            HttpResponse.badRequest("Upload Failed")
        }
    }
}

File upload with metadata

import io.micronaut.http.HttpResponse
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Post
import io.micronaut.http.multipart.CompletedFileUpload

import java.io.File
import java.io.IOException
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths

@Controller("/upload")
class CompletedUploadController {

    @Post(value = "/completed",
            consumes = [MediaType.MULTIPART_FORM_DATA],
            produces = [MediaType.TEXT_PLAIN]) <b>(1)</b>
    fun uploadCompleted(file: CompletedFileUpload): HttpResponse<String> { <b>(2)</b>
        return try {
            val tempFile = File.createTempFile(file.filename, "temp") <b>(3)</b>
            val path = Paths.get(tempFile.absolutePath)
            Files.write(path, file.bytes) <b>(3)</b>
            HttpResponse.ok("Uploaded")
        } catch (exception: IOException) {
            HttpResponse.badRequest("Upload Failed")
        }

    }
}
1 The method is set to consumeMULTIPART_FORM_DATA
2 The method parameters match form attribute names. In this case the file will match for example an <input type="file" name="file">
3 The CompletedFileUpload instance gives access to metadata about the upload as well as access to the file’s contents.

Multiple Uploads

Different Names

If a multipart request supplies multiple uploads that each have a different part name, simply create an argument to your route that receives each part. For example:

HttpResponse upload(String title, String name)

A route method signature like the above will expect 2 different parts with the names "title" and "name".

Same Name

To handle receiving multiple parts with the same part name, the argument must be a Publisher . When used in one of the following ways, the publisher will emit one item per file found with the specified name. The publisher must accept one of the following types:

For example:

HttpResponse upload(Publisher<StreamingFileUpload> files)
HttpResponse upload(Publisher<CompletedFileUpload> files)
HttpResponse upload(Publisher<MyObject> files)
HttpResponse upload(Publisher<Publisher<PartData>> files)

Whole Body Binding

For the cases where the names of the parts of the request cannot be known ahead of time, or the entire body should be read, a special type can be used to indicate the entire body is desired.

If a route has an argument of typeMultipartBody (not to be confused with the class for the client) annotated with@Body, then each part of the request will be emitted through the argument. AMultipartBody is a publisher ofCompletedPart instances.

For example:

Binding to the entire multipart body

import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Body;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Post;
import io.micronaut.http.multipart.CompletedFileUpload;
import io.micronaut.http.multipart.CompletedPart;
import io.micronaut.http.server.multipart.MultipartBody;
import io.reactivex.Single;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;

@Controller("/upload")
public class WholeBodyUploadController {

    @Post(value = "/whole-body",
            consumes = MediaType.MULTIPART_FORM_DATA,
            produces = MediaType.TEXT_PLAIN) <b>(1)</b>
    public Single<String> uploadBytes(@Body MultipartBody body) { <b>(2)</b>
        return Single.create(emitter -> {
            body.subscribe(new Subscriber<CompletedPart>() {
                private Subscription s;

                @Override
                public void onSubscribe(Subscription s) {
                    this.s = s;
                    s.request(1);
                }

                @Override
                public void onNext(CompletedPart completedPart) {
                    String partName = completedPart.getName();
                    if (completedPart instanceof CompletedFileUpload) {
                        String originalFileName = ((CompletedFileUpload) completedPart).getFilename();
                    }
                }

                @Override
                public void onError(Throwable t) {
                    emitter.onError(t);
                }

                @Override
                public void onComplete() {
                    emitter.onSuccess("Uploaded");
                }
            });
        });
    }
}

Binding to the entire multipart body

import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Body
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Post
import io.micronaut.http.multipart.CompletedFileUpload
import io.micronaut.http.multipart.CompletedPart
import io.micronaut.http.server.multipart.MultipartBody
import io.reactivex.Single
import org.reactivestreams.Subscriber
import org.reactivestreams.Subscription

@Controller("/upload")
class WholeBodyUploadController {

    @Post(value = "/whole-body",
            consumes = MediaType.MULTIPART_FORM_DATA,
            produces = MediaType.TEXT_PLAIN) <b>(1)</b>
    Single<String> uploadBytes(@Body MultipartBody body) { <b>(2)</b>
        Single.<String>create({ emitter ->
            body.subscribe(new Subscriber<CompletedPart>() {
                private Subscription s

                @Override
                void onSubscribe(Subscription s) {
                    this.s = s
                    s.request(1)
                }

                @Override
                void onNext(CompletedPart completedPart) {
                    String partName = completedPart.name
                    if (completedPart instanceof CompletedFileUpload) {
                        String originalFileName = ((CompletedFileUpload) completedPart).filename
                    }
                }

                @Override
                void onError(Throwable t) {
                    emitter.onError(t)
                }

                @Override
                void onComplete() {
                    emitter.onSuccess("Uploaded")
                }
            })
        })
    }
}

Binding to the entire multipart body

import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Body
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Post
import io.micronaut.http.multipart.CompletedFileUpload
import io.micronaut.http.multipart.CompletedPart
import io.micronaut.http.server.multipart.MultipartBody
import io.reactivex.Single
import org.reactivestreams.Subscriber
import org.reactivestreams.Subscription

@Controller("/upload")
class WholeBodyUploadController {

    @Post(value = "/whole-body",
            consumes = [MediaType.MULTIPART_FORM_DATA],
            produces = [MediaType.TEXT_PLAIN]) <b>(1)</b>
    fun uploadBytes(@Body body: MultipartBody): Single<String> { <b>(2)</b>
        return Single.create { emitter ->
            body.subscribe(object : Subscriber<CompletedPart> {
                private var s: Subscription? = null

                override fun onSubscribe(s: Subscription) {
                    this.s = s
                    s.request(1)
                }

                override fun onNext(completedPart: CompletedPart) {
                    val partName = completedPart.name
                    if (completedPart is CompletedFileUpload) {
                        val originalFileName = completedPart.filename
                    }
                }

                override fun onError(t: Throwable) {
                    emitter.onError(t)
                }

                override fun onComplete() {
                    emitter.onSuccess("Uploaded")
                }
            })
        }
    }
}

6.20 File Transfers

Micronaut supports the sending of files to the client in a couple of easy ways.

Sending File Objects

It is possible to simply return a File object from your controller method and the data will be returned to the client. The Content-Type header of file responses will be calculated based on the name of the file.

To control either the media type of the file being sent, or to set the file to be downloaded (i.e. using the Content-Disposition header) you should instead construct anSystemFile with the file object you would like to be used. For example:

Sending a SystemFile

@Get
public SystemFile download() {
    File file = ...
    return new SystemFile(file).attach("myfile.txt");
    // or new SystemFile(file, MediaType.TEXT_HTML_TYPE)
}

Sending an InputStream

For cases where a reference to a File object is not possible (for example resources contained within JAR files), Micronaut supports transferring of input streams. To return a stream of data from the controller method, construct aStreamedFile.

The constructor for StreamedFile also accepts a java.net.URL for your convenience.

Sending a StreamedFile

@Get
public StreamedFile download() {
    InputStream inputStream = ...
    return new StreamedFile(inputStream, MediaType.TEXT_PLAIN_TYPE)
    // An attach(String filename) method is also available to set the Content-Disposition
}

The server supports returning 304 (Not Modified) responses if the files being transferred have not changed and the request contains the appropriate header. In addition, if the client accepts encoded responses, Micronaut will encode the file if it is deemed appropriate. Encoding will happen if the file is text based and greater than 1KB by default. The threshold at which data will be encoded is configurable. See the server configuration reference for details.

To use a custom data source to send data through an input stream, construct a PipedInputStream and PipedOutputStream to allow writing data from the output stream to the input. Make sure to do the work on a separate thread so the file can be returned immediately.

Cache Configuration

By default file responses will include caching headers. The following options determine how the Cache-Control header is built.

Table 1. Configuration Properties for FileTypeHandlerConfiguration
Property Type Description

netty.responses.file.cache-seconds

int

Cache Seconds. Default value (60).

Table 2. Configuration Properties for FileTypeHandlerConfiguration$CacheControlConfiguration
Property Type Description

netty.responses.file.cache-control.public

boolean

Sets whether the cache control is public. Default value (false)

6.21 HTTP Filters

The Micronaut HTTP server supports the ability to apply filters to request/response processing in a similar, but reactive, way to Servlet filters in traditional Java applications.

Filters provide the ability to support the following use cases:

  • Decoration of the incomingHttpRequest

  • Modification of the outgoingHttpResponse

  • Implementation of cross cutting concerns such as security, tracing etc.

For a server application, theHttpServerFilter interface’s doFilter method can be implemented.

The doFilter method accepts theHttpRequest and an instance ofServerFilterChain.

The ServerFilterChain interface contains a resolved chain of filters with the final entry in the chain being the matched route. The ServerFilterChain.proceed(io.micronaut.http.HttpRequest) method can be used to resume processing of the request.

The proceed(..) method returns a Reactive Streams Publisher that emits the response that will be returned to the client. Implementors of filters can subscribe to the Publisher and mutate the emittedMutableHttpResponse object to modify the response prior to returning the response to the client.

To put these concepts into practise lets look at an example.

Filters execute in the event loop therefore blocking operations must be offloaded to another thread pool.

Writing a Filter

Consider a hypothetical use case whereby you wish to trace each request to the Micronaut "Hello World" example using some external system. The external system could be a database, a distributed tracing service and may require I/O operations.

What you don’t want to do is block the underlying Netty event loop within your filter, instead you want the filter to proceed with execution once any I/O is complete.

As an example, consider the following example TraceService that uses RxJava to compose an I/O operation:

A TraceService Example using RxJava

import io.micronaut.http.HttpRequest;
import io.reactivex.Flowable;
import io.reactivex.schedulers.Schedulers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Singleton;

@Singleton
public class TraceService {

    private static final Logger LOG = LoggerFactory.getLogger(TraceService.class);

    Flowable<Boolean> trace(HttpRequest<?> request) {
        return Flowable.fromCallable(() -> { <b>(1)</b>
            if (LOG.isDebugEnabled()) {
                LOG.debug("Tracing request: " + request.getUri());
            }
            // trace logic here, potentially performing I/O <b>(2)</b>
            return true;
        }).subscribeOn(Schedulers.io()); <b>(3)</b>
    }
}

A TraceService Example using RxJava

import io.micronaut.http.HttpRequest
import io.reactivex.Flowable
import io.reactivex.schedulers.Schedulers
import org.slf4j.Logger
import org.slf4j.LoggerFactory

import javax.inject.Singleton

@Singleton
class TraceService {

    private static final Logger LOG = LoggerFactory.getLogger(TraceService.class)

    Flowable<Boolean> trace(HttpRequest<?> request) {
        Flowable.fromCallable({ ->  <b>(1)</b>
            if (LOG.isDebugEnabled()) {
                LOG.debug("Tracing request: " + request.getUri())
            }
            // trace logic here, potentially performing I/O <b>(2)</b>
            return true
        }).subscribeOn(Schedulers.io()) <b>(3)</b>
    }
}

A TraceService Example using RxJava

import io.micronaut.http.HttpRequest
import io.reactivex.Flowable
import io.reactivex.schedulers.Schedulers
import org.slf4j.LoggerFactory

import javax.inject.Singleton


@Singleton
class TraceService {

    private val LOG = LoggerFactory.getLogger(TraceService::class.java)

    internal fun trace(request: HttpRequest<*>): Flowable<Boolean> {
        return Flowable.fromCallable {
            <b>(1)</b>
            if (LOG.isDebugEnabled) {
                LOG.debug("Tracing request: " + request.uri)
            }
            // trace logic here, potentially performing I/O <b>(2)</b>
            true
        }.subscribeOn(Schedulers.io()) <b>(3)</b>
    }
}
1 The Flowable type is used to create logic that executes potentially blocking operations to write the trace data from the request
2 Since this is just an example the logic does nothing and a place holder comment is used
3 The RxJava I/O scheduler is used to execute the logic

You can then inject this implementation into your filter definition:

An Example HttpServerFilter

import io.micronaut.http.*;
import io.micronaut.http.annotation.Filter;
import io.micronaut.http.filter.*;
import org.reactivestreams.Publisher;

@Filter("/hello/**") <b>(1)</b>
public class TraceFilter implements HttpServerFilter { <b>(2)</b>
    private final TraceService traceService;

    public TraceFilter(TraceService traceService) { <b>(3)</b>
        this.traceService = traceService;
    }

}

An Example HttpServerFilter

import io.micronaut.http.HttpRequest
import io.micronaut.http.MutableHttpResponse
import io.micronaut.http.annotation.Filter
import io.micronaut.http.filter.HttpServerFilter
import io.micronaut.http.filter.ServerFilterChain
import org.reactivestreams.Publisher

@Filter("/hello/**") <b>(1)</b>
class TraceFilter implements HttpServerFilter { <b>(2)</b>
    private final TraceService traceService

    TraceFilter(TraceService traceService) { <b>(3)</b>
        this.traceService = traceService
    }

}

An Example HttpServerFilter

import io.micronaut.http.HttpRequest
import io.micronaut.http.MutableHttpResponse
import io.micronaut.http.annotation.Filter
import io.micronaut.http.filter.HttpServerFilter
import io.micronaut.http.filter.ServerFilterChain
import org.reactivestreams.Publisher


@Filter("/hello/**") <b>(1)</b>
class TraceFilter(<b>(2)</b>
        private val traceService: TraceService)<b>(3)</b>
    : HttpServerFilter {

}
1 TheFilter annotation is used to define the URI patterns the filter matches
2 The class implements theHttpServerFilter interface
3 The previously defined TraceService is injected via a constructor argument

The final step is write the doFilter implementation of theHttpServerFilter interface.

The doFilter implementation

@Override
    public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request, ServerFilterChain chain) {
        return traceService.trace(request) <b>(1)</b>
                           .switchMap(aBoolean -> chain.proceed(request)) <b>(2)</b>
                           .doOnNext(res -> <b>(3)</b>
                                res.getHeaders().add("X-Trace-Enabled", "true")
                           );
    }

The doFilter implementation

@Override
    Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request, ServerFilterChain chain) {
        traceService.trace(request) <b>(1)</b>
                           .switchMap({ aBoolean -> chain.proceed(request) }) <b>(2)</b>
                           .doOnNext({ res -> <b>(3)</b>
                               res.getHeaders().add("X-Trace-Enabled", "true")
                           })
    }

The doFilter implementation

override fun doFilter(request: HttpRequest<*>, chain: ServerFilterChain): Publisher<MutableHttpResponse<*>> {
        return traceService.trace(request) <b>(1)</b>
                .switchMap { aBoolean -> chain.proceed(request) } <b>(2)</b>
                .doOnNext { res ->
                    <b>(3)</b>
                    res.headers.add("X-Trace-Enabled", "true")
                }
    }
1 The previously defined TraceService is called to trace the request
2 If the trace call succeeds then the filter switches back to resuming the request processing using RxJava’s switchMap method, which invokes the proceed method of theServerFilterChain
3 Finally, RxJava’s doOnNext method is used to add a header called X-Trace-Enabled to the response.

The previous example demonstrates some key concepts such as executing logic in a non-blocking matter before proceeding with the request and modifying the outgoing response.

The examples use RxJava, however you can use any reactive framework that supports the Reactive streams specifications

6.22 HTTP Sessions

By default Micronaut is a stateless HTTP server, however depending on your application requirements you may need the notion of HTTP sessions.

Micronaut comes with a session module inspired by Spring Session that enables this that currently features two implementations:

  • In-Memory sessions - which you should combine with an a sticky sessions proxy if you plan to run multiple instances.

  • Redis sessions - In this case Redis is used to store sessions and non-blocking I/O is used to read/write sessions to Redis.

Enabling Sessions

To enable support for in-memory sessions you just need the session dependency:

implementation("io.micronaut:micronaut-session")
<dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-session</artifactId>
</dependency>

Redis Sessions

If you wish to storeSession instances in Redis you can do so with the Micronaut Redis module which includes detailed instructions.

To quickly get up and running with Redis sessions you must also have the redis-lettuce configuration on your classpath:

build.gradle

compile "io.micronaut:micronaut-session"
compile "io.micronaut.redis:micronaut-redis-lettuce"

And enable Redis sessions via configuration in application.yml :

Enabling Redis Sessions

redis:
    uri: redis://localhost:6379
micronaut:
    session:
        http:
            redis:
                enabled: true

Configuring Session Resolution

How theSession is resolved can be configured with HttpSessionConfiguration .

By default sessions are resolved using anHttpSessionFilter that looks up session identifiers via either an HTTP header (using the Authorization-Info or X-Auth-Token header values) or via a Cookie called SESSION .

If you wish to disable either header resolution or cookie resolution you can via configuration in application.yml :

Disabling Cookie Resolution

micronaut:
    session:
        http:
            cookie: false
            header: true

The above configuration enables header resolution, but disables cookie resolution. You can also configure the header or cookie names as necessary.

Working with Sessions

ASession object can be retrieved simply by declaring theSession in a controller method signature. For example consider the following controller:

import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Post;
import io.micronaut.session.Session;
import io.micronaut.session.annotation.SessionValue;

import edu.umd.cs.findbugs.annotations.Nullable;
import javax.validation.constraints.NotBlank;

@Controller("/shopping")
public class ShoppingController {
    private static final String ATTR_CART = "cart"; <b>(1)</b>

    @Post("/cart/{name}")
    Cart addItem(Session session, @NotBlank String name) { <b>(2)</b>
        Cart cart = session.get(ATTR_CART, Cart.class).orElseGet(() -> { <b>(3)</b>
            Cart newCart = new Cart();
            session.put(ATTR_CART, newCart); <b>(4)</b>
            return newCart;
        });
        cart.getItems().add(name);
        return cart;
    }

}
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.annotation.Post
import io.micronaut.session.Session
import io.micronaut.session.annotation.SessionValue

import javax.annotation.Nullable
import javax.validation.constraints.NotBlank

@Controller("/shopping")
class ShoppingController {
    static final String ATTR_CART = "cart" <b>(1)</b>

    @Post("/cart/{name}")
    Cart addItem(Session session, @NotBlank String name) { <b>(2)</b>
        Cart cart = session.get(ATTR_CART, Cart.class).orElseGet({ ->  <b>(3)</b>
            Cart newCart = new Cart()
            session.put(ATTR_CART, newCart) <b>(4)</b>
            newCart
        })
        cart.getItems().add(name)
        cart
    }

}
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.annotation.Post
import io.micronaut.session.Session
import io.micronaut.session.annotation.SessionValue


@Controller("/shopping")
class ShoppingController {

    @Post("/cart/{name}")
    internal fun addItem(session: Session, name: String): Cart { <b>(2)</b>
        require(name.isNotBlank()) { "Name cannot be blank" }
        val cart = session.get(ATTR_CART, Cart::class.java).orElseGet({
            <b>(3)</b>
            val newCart = Cart()
            session.put(ATTR_CART, newCart) <b>(4)</b>
            newCart
        })
        cart.items.add(name)
        return cart
    }

}
1 The ShoppingController declares aSession attribute called cart
2 TheSession is declared as a parameter to the method
3 The cart attribute is retrieved
4 Otherwise a new Cart instance is created and stored in the session

Note that because theSession is declared as a required parameter to the execute the controller action, theSession will be created and saved to theSessionStore.

If you don’t want to create unnecessary sessions then you can declare theSession as @Nullable in which case a session will not be created and saved unnecessarily. For example:

Using @Nullable with Sessions

@Post("/cart/clear")
    void clearCart(@Nullable Session session) {
        if (session != null) {
            session.remove(ATTR_CART);
        }
    }

Using @Nullable with Sessions

@Post("/cart/clear")
    void clearCart(@Nullable Session session) {
        if (session != null) {
            session.remove(ATTR_CART)
        }
    }

Using @Nullable with Sessions

@Post("/cart/clear")
    internal fun clearCart(session: Session?) {
        session?.remove(ATTR_CART)
    }

The above method will only create and inject a newSession if one already exists.

Session Clients

If the client is a web browser then sessions should just work if you have cookies is enabled. However for programmatic HTTP clients you need to make sure you propagate the session id between HTTP calls.

For example, when invoking the viewCart method of the StoreController in the previous example the HTTP client will receive by default a AUTHORIZATION_INFO header. The following example, using a Spock test, demonstrates this:

HttpResponse<Cart> response = client.exchange(HttpRequest.GET("/shopping/cart"), Cart.class) <b>(1)</b>
                                                .blockingFirst();
        Cart cart = response.body();

        assertNotNull(response.header(HttpHeaders.AUTHORIZATION_INFO)); <b>(2)</b>
        assertNotNull(cart);
        assertTrue(cart.getItems().isEmpty());
when: "The shopping cart is retrieved"
        HttpResponse<Cart> response = httpClient.exchange(HttpRequest.GET('/shopping/cart'), Cart) <b>(1)</b>
                                                .blockingFirst()
        Cart cart = response.body()

        then: "The shopping cart is present as well as a session id header"
        response.header(HttpHeaders.AUTHORIZATION_INFO) != null <b>(2)</b>
        cart != null
        cart.items.isEmpty()
var response = client.exchange(HttpRequest.GET<Cart>("/shopping/cart"), Cart::class.java) <b>(1)</b>
                    .blockingFirst()
            var cart = response.body()

            assertNotNull(response.header(HttpHeaders.AUTHORIZATION_INFO)) <b>(2)</b>
            assertNotNull(cart)
            cart.items.isEmpty()
1 A request is made to /shopping/cart
2 The AUTHORIZATION_INFO header is present in the response

You can then pass this AUTHORIZATION_INFO in subsequent requests to re-use the existingSession:

String sessionId = response.header(HttpHeaders.AUTHORIZATION_INFO); <b>(1)</b>

        response = client.exchange(
                HttpRequest.POST("/shopping/cart/Apple", "")
                        .header(HttpHeaders.AUTHORIZATION_INFO, sessionId), Cart.class) <b>(2)</b>
                .blockingFirst();
        cart = response.body();
String sessionId = response.header(HttpHeaders.AUTHORIZATION_INFO) <b>(1)</b>

        response = httpClient.exchange(
                HttpRequest.POST('/shopping/cart/Apple', "")
                        .header(HttpHeaders.AUTHORIZATION_INFO, sessionId), Cart) <b>(2)</b>
                .blockingFirst()
        cart = response.body()
val sessionId = response.header(HttpHeaders.AUTHORIZATION_INFO) <b>(1)</b>

            response = client.exchange(
                    HttpRequest.POST("/shopping/cart/Apple", "")
                            .header(HttpHeaders.AUTHORIZATION_INFO, sessionId), Cart::class.java) <b>(2)</b>
                    .blockingFirst()
            cart = response.body()
1 The AUTHORIZATION_INFO is retrieved from the response
2 And then sent as a header in the subsequent request

Using @SessionValue

Rather than explicitly injecting theSession into a controller method you can instead use@SessionValue. For example:

Using @SessionValue

@Get("/cart")
    @SessionValue(ATTR_CART) <b>(1)</b>
    Cart viewCart(@SessionValue @Nullable Cart cart) { <b>(2)</b>
        if (cart == null) {
            cart = new Cart();
        }
        return cart;
    }

Using @SessionValue

@Get("/cart")
    @SessionValue("cart") <b>(1)</b>
    Cart viewCart(@SessionValue @Nullable Cart cart) { <b>(2)</b>
        if (cart == null) {
            cart = new Cart()
        }
        cart
    }

Using @SessionValue

@Get("/cart")
    @SessionValue(ATTR_CART) <b>(1)</b>
    internal fun viewCart(@SessionValue cart: Cart?): Cart { <b>(2)</b>
        var cart = cart
        if (cart == null) {
            cart = Cart()
        }
        return cart
    }
1 @SessionValue is declared on the method resulting in the return value being stored in theSession. Note that you must specify the attribute name when used on a return value
2 @SessionValue is used on a @Nullable parameter which results in looking up the value from theSession in a non-blocking way and supplying it if present. In the case a value is not specified to@SessionValue resulting in the parameter name being used to lookup the attribute.

Session Events

You can register ApplicationEventListener beans to listen forSession related events located in the io.micronaut.session.event package.

The following table summarizes the events:

Table 1. Session Events
Type Description

SessionCreatedEvent

Fired when aSession is created

SessionDeletedEvent

Fired when aSession is deleted

SessionExpiredEvent

Fired when aSession expires

SessionDestroyedEvent

Parent of both SessionDeletedEvent and SessionExpiredEvent

6.23 Server Sent Events

The Micronaut HTTP server supports emitting Server Sent Events (SSE) using theEvent API.

To emit events from the server you simply return a Reactive Streams Publisher that emits objects of typeEvent.

The Publisher itself could publish events from a background task, via an event system or whatever.

Imagine for an example a event stream of news headlines, you may define a data class as follows:

Headline

public class Headline {
    private String title;
    private String description;

    public Headline() { }

    public Headline(String title, String description) {
        this.title = title;
        this.description = description;
    }

    public String getTitle() {
        return title;
    }

    public String getDescription() {
        return description;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

Headline

class Headline {
    String title;
    String description;

    Headline() {}

    Headline(String title, String description) {
        this.title = title;
        this.description = description;
    }
}

Headline

class Headline {
    var title: String? = null
    var description: String? = null

    constructor() {}

    constructor(title: String, description: String) {
        this.title = title
        this.description = description
    }
}

To emit news headline events you can write a controller that returns a Publisher ofEvent instances using which ever Reactive library you prefer. The example below uses RxJava 2’s Flowable via the generate method:

Publishing Server Sent Events from a Controller

import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.*;
import io.micronaut.http.sse.Event;
import io.micronaut.scheduling.TaskExecutors;
import io.micronaut.scheduling.annotation.ExecuteOn;
import io.reactivex.Flowable;
import org.reactivestreams.Publisher;

@Controller("/headlines")
public class HeadlineController {

    @ExecuteOn(TaskExecutors.IO)
    @Get(produces = MediaType.TEXT_EVENT_STREAM)
    public Publisher<Event<Headline>> index() { <b>(1)</b>
        String[] versions = new String[]{"1.0", "2.0"}; <b>(2)</b>

        return Flowable.generate(() -> 0, (i, emitter) -> { <b>(3)</b>
            if (i < versions.length) {
                emitter.onNext( <b>(4)</b>
                    Event.of(new Headline("Micronaut " + versions[i] + " Released", "Come and get it"))
                );
            } else {
                emitter.onComplete(); <b>(5)</b>
            }
            return ++i;
        });
    }
}

Publishing Server Sent Events from a Controller

import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.sse.Event
import io.micronaut.scheduling.TaskExecutors
import io.micronaut.scheduling.annotation.ExecuteOn
import io.reactivex.Emitter
import io.reactivex.Flowable
import io.reactivex.functions.BiFunction
import org.reactivestreams.Publisher


@Controller("/headlines")
class HeadlineController {

    @ExecuteOn(TaskExecutors.IO)
    @Get(produces = MediaType.TEXT_EVENT_STREAM)
    Publisher<Event<Headline>> index() { <b>(1)</b>
        String[] versions = ["1.0", "2.0"] <b>(2)</b>

        def initialState = { -> 0 }
        def emitterFunction = { Integer i, Emitter emitter ->  <b>(3)</b>
            if (i < versions.length) {
                emitter.onNext( <b>(4)</b>
                        Event.of(new Headline("Micronaut " + versions[i] + " Released", "Come and get it"))
                )
            } else {
                emitter.onComplete() <b>(5)</b>
            }
            return ++i
        }

        return Flowable.generate(initialState, emitterFunction as BiFunction<Integer,Emitter<Event<Headline>>,Integer>)
    }
}

Publishing Server Sent Events from a Controller

import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.sse.Event
import io.micronaut.scheduling.TaskExecutors
import io.micronaut.scheduling.annotation.ExecuteOn
import io.reactivex.Emitter
import io.reactivex.Flowable
import io.reactivex.functions.BiFunction
import org.reactivestreams.Publisher
import java.util.concurrent.Callable


@Controller("/headlines")
class HeadlineController {

    @ExecuteOn(TaskExecutors.IO)
    @Get(produces = [MediaType.TEXT_EVENT_STREAM])
    fun index(): Publisher<Event<Headline>> { <b>(1)</b>
        val versions = arrayOf("1.0", "2.0") <b>(2)</b>

        return Flowable.generate<Event<Headline>, Int>(Callable<Int>{ 0 }, BiFunction { <b>(3)</b>
            i: Int, emitter: Emitter<Event<Headline>> ->
            var nextInt: Int = i
            if (i < versions.size) {
                emitter.onNext( <b>(4)</b>
                        Event.of<Headline>(Headline("Micronaut " + versions[i] + " Released", "Come and get it"))
                )
            } else {
                emitter.onComplete() <b>(5)</b>
            }
            ++nextInt
        })
    }
}
1 The controller method returns a Publisher ofEvent
2 For each version of Micronaut a headline is emitted
3 The Flowable type’s generate method is used to generate a Publisher . The generate method accepts an initial value and a lambda that accepts the value and a Emitter . Note that this example executes on the same thread as the controller action, but you could use subscribeOn or map and existing "hot" Flowable .
4 The Emitter interface’s onNext method is used to emit objects of typeEvent. TheEvent.of(ET) factory method is used to construct the event.
5 The Emitter interface’s onComplete method is used to indicate when to finish sending server sent events.
You typically want to schedule SSE event streams on a separate executor. The previous example uses@ExecuteOn to execute the stream on the I/O executor.

The above example will send back a response of type text/event-stream and for eachEvent emitted the Headline type previously will be converted to JSON resulting in responses such as:

Server Sent Event Response Output

data: {"title":"Micronaut 1.0 Released","description":"Come and get it"}
 data: {"title":"Micronaut 2.0 Released","description":"Come and get it"}

You can use the methods of theEvent interface to customize the Server Sent Event data sent back including associating event ids, comments, retry timeouts etc.

6.24 WebSocket Support

Micronaut features dedicated support for creating WebSocket clients and servers. The io.micronaut.websocket.annotation package includes a set of annotations for defining both clients and servers.

6.24.1 Using @ServerWebSocket

The@ServerWebSocket annotation can be applied to any class that should map to a WebSocket URI. The following example is a simple chat WebSocket implementation:

WebSocket Chat Example

import io.micronaut.websocket.WebSocketBroadcaster;
import io.micronaut.websocket.WebSocketSession;
import io.micronaut.websocket.annotation.OnClose;
import io.micronaut.websocket.annotation.OnMessage;
import io.micronaut.websocket.annotation.OnOpen;
import io.micronaut.websocket.annotation.ServerWebSocket;

import java.util.function.Predicate;

@ServerWebSocket("/chat/{topic}/{username}") <b>(1)</b>
public class ChatServerWebSocket {
    private WebSocketBroadcaster broadcaster;

    public ChatServerWebSocket(WebSocketBroadcaster broadcaster) {
        this.broadcaster = broadcaster;
    }

    @OnOpen <b>(2)</b>
    public void onOpen(String topic, String username, WebSocketSession session) {
        String msg = "[" + username + "] Joined!";
        broadcaster.broadcastSync(msg, isValid(topic, session));
    }

    @OnMessage <b>(3)</b>
    public void onMessage(
            String topic,
            String username,
            String message,
            WebSocketSession session) {
        String msg = "[" + username + "] " + message;
        broadcaster.broadcastSync(msg, isValid(topic, session)); <b>(4)</b>
    }

    @OnClose <b>(5)</b>
    public void onClose(
            String topic,
            String username,
            WebSocketSession session) {
        String msg = "[" + username + "] Disconnected!";
        broadcaster.broadcastSync(msg, isValid(topic, session));
    }

    private Predicate<WebSocketSession> isValid(String topic, WebSocketSession session) {
        return s -> s != session && topic.equalsIgnoreCase(s.getUriVariables().get("topic", String.class, null));
    }
}

WebSocket Chat Example

import io.micronaut.websocket.WebSocketBroadcaster
import io.micronaut.websocket.WebSocketSession
import io.micronaut.websocket.annotation.OnClose
import io.micronaut.websocket.annotation.OnMessage
import io.micronaut.websocket.annotation.OnOpen
import io.micronaut.websocket.annotation.ServerWebSocket

import java.util.function.Predicate

@ServerWebSocket("/chat/{topic}/{username}") <b>(1)</b>
class ChatServerWebSocket {
    private WebSocketBroadcaster broadcaster

    ChatServerWebSocket(WebSocketBroadcaster broadcaster) {
        this.broadcaster = broadcaster
    }

    @OnOpen <b>(2)</b>
    void onOpen(String topic, String username, WebSocketSession session) {
        String msg = "[" + username + "] Joined!"
        broadcaster.broadcastSync(msg, isValid(topic, session))
    }

    @OnMessage <b>(3)</b>
     void onMessage(
            String topic,
            String username,
            String message,
            WebSocketSession session) {
        String msg = "[" + username + "] " + message
        broadcaster.broadcastSync(msg, isValid(topic, session)) <b>(4)</b>
    }

    @OnClose <b>(5)</b>
     void onClose(
            String topic,
            String username,
            WebSocketSession session) {
        String msg = "[" + username + "] Disconnected!"
        broadcaster.broadcastSync(msg, isValid(topic, session))
    }

    private Predicate<WebSocketSession> isValid(String topic, WebSocketSession session) {
        return { s -> s != session && topic.equalsIgnoreCase(s.getUriVariables().get("topic", String.class, null)) }
    }
}

WebSocket Chat Example

import io.micronaut.websocket.WebSocketBroadcaster
import io.micronaut.websocket.WebSocketSession
import io.micronaut.websocket.annotation.OnClose
import io.micronaut.websocket.annotation.OnMessage
import io.micronaut.websocket.annotation.OnOpen
import io.micronaut.websocket.annotation.ServerWebSocket

import java.util.function.Predicate

@ServerWebSocket("/chat/{topic}/{username}") <b>(1)</b>
class ChatServerWebSocket(private val broadcaster: WebSocketBroadcaster) {

    @OnOpen <b>(2)</b>
    fun onOpen(topic: String, username: String, session: WebSocketSession) {
        val msg = "[$username] Joined!"
        broadcaster.broadcastSync(msg, isValid(topic, session))
    }

    @OnMessage <b>(3)</b>
    fun onMessage(
            topic: String,
            username: String,
            message: String,
            session: WebSocketSession) {
        val msg = "[$username] $message"
        broadcaster.broadcastSync(msg, isValid(topic, session)) <b>(4)</b>
    }

    @OnClose <b>(5)</b>
    fun onClose(
            topic: String,
            username: String,
            session: WebSocketSession) {
        val msg = "[$username] Disconnected!"
        broadcaster.broadcastSync(msg, isValid(topic, session))
    }

    private fun isValid(topic: String, session: WebSocketSession): Predicate<WebSocketSession> {
        return Predicate<WebSocketSession>{ s -> (s !== session && topic.equals(s.getUriVariables().get("topic", String::class.java, null), ignoreCase = true)) }
    }
}
1 The@ServerWebSocket annotation is used to define the path the WebSocket is mapped under. The URI can be a URI template.
2 The@OnOpen annotation is used to declare a method that is invoked when the WebSocket is opened.
3 The@OnMessage annotation is used to declare a method that is invoked when a message is received.
4 You can use aWebSocketBroadcaster to broadcast messages to every WebSocket session. You can filter which sessions to communicate with a Predicate . Also, you could use the passedWebSocketSession instance to send a message to it with WebSocketSession::send .
5 The@OnClose annotation is used to declare a method that is invoked when the WebSocket is closed.
A working example of WebSockets in action can be found in the Micronaut Examples GitHub repository.

In terms of binding the method arguments to each WebSocket method can be:

  • A variable from the URI template (in the above example topic and username are variables in the URI template)

  • An instance ofWebSocketSession

The @OnClose Method

The@OnClose method can also optionally receive aCloseReason. The @OnClose method is invoked prior to the session closing.

The @OnMessage Method

The@OnMessage method can define a parameter that is the message body. The parameter can be one of the following:

  • A Netty WebSocketFrame

  • Any Java primitive or simple type (such as String ). In fact any type that can be converted from ByteBuf (you can register additionalTypeConverter beans if you wish to support a custom type).

  • A byte[] , a ByteBuf or a Java NIO ByteBuffer .

  • A Plain Old Java Object (POJO). In the case of a POJO the POJO will be decoded by default as JSON usingJsonMediaTypeCodec. You can register a custom codec if necessary and define the content type of the handler using the@Consumes annotation.

The @OnError Method

A method annotated with@OnError can be added to implement custom error handling. The @OnError method can optionally define a parameter that receives the exception type that is to be handled. If no @OnError handling is present and a unrecoverable exception occurs the WebSocket is automatically closed.

Non-Blocking Message Handling

The previous example uses the broadcastSync method of theWebSocketBroadcaster interface which blocks until the broadcast is complete. A similar sendSync method exists inWebSocketSession to send a message to a single receiver in a blocking manner. You can however implement non-blocking WebSocket servers by instead returning a Publisher or a Future from each WebSocket handler method. For example:

WebSocket Chat Example

@OnMessage
    public Publisher<Message> onMessage(
            String topic,
            String username,
            Message message,
            WebSocketSession session) {

        String text = "[" + username + "] " + message.getText();
        Message newMessage = new Message(text);
        return broadcaster.broadcast(newMessage, isValid(topic, session));
    }

WebSocket Chat Example

@OnMessage
     Publisher<Message> onMessage(
            String topic,
            String username,
            Message message,
            WebSocketSession session) {

        String text = "[" + username + "] " + message.getText()
        Message newMessage = new Message(text)
        broadcaster.broadcast(newMessage, isValid(topic, session))
    }

WebSocket Chat Example

@OnMessage
    fun onMessage(
            topic: String,
            username: String,
            message: Message,
            session: WebSocketSession): Publisher<Message> {

        val text = "[" + username + "] " + message.text
        val newMessage = Message(text)
        return broadcaster.broadcast(newMessage, isValid(topic, session))
    }

The example above uses broadcast , which creates an instance of Publisher and returns the value to Micronaut. Micronaut will then take care of sending the message asynchronously based on the Publisher interface. The similar send method can be used to send a single message asynchronously via Micronaut return value.

For sending messages asynchronously outside Micronaut annotated handler methods, you can use broadcastAsync and sendAsync methods in their respectiveWebSocketBroadcaster andWebSocketSession interfaces. For blocking sends, the broadcastSync and sendSync methods can be used.

@ServerWebSocket and Scopes

By default a unique @ServerWebSocket instance is created for each WebSocket connection. This allows you to retrieve theWebSocketSession from the @OnOpen handler and assign it to a field of the @ServerWebSocket instance.

If you define the @ServerWebSocket as @Singleton it should be noted that extra care will need to be taken to synchronize local state to avoid thread safety issues.

Sharing Sessions with the HTTP Session

TheWebSocketSession is by default backed by an in-memory map. If you add the the session module you can however sharebetween the HTTP server and the WebSocket server.

When sessions are backed by a persistent store such as Redis then after each message is processed the session is updated to the backing store.

Using the CLI

If you have created your project using the Micronaut CLI and the default ( service ) profile, you can use the create-websocket-server command to create a class withWebSocketServer.

$ mn create-websocket-server MyChat
| Rendered template WebsocketServer.java to destination src/main/java/example/MyChatServer.java

Connection Timeouts

By default Micronaut will timeout idle connections that have no activity after 5 minutes. Normally this is not a problem as browsers will automatically reconnect WebSocket sessions, however you can control this behaviour by setting the micronaut.server.idle-timeout setting (a negative value will result no timeout):

Setting the Connection Timeout for the Server

micronaut:
    server:
        idle-timeout: 30m # 30 minutes

If you are using Micronaut’s WebSocket client then you may also wish to set the timeout on the client:

Setting the Connection Timeout for the Client

micronaut:
    http:
        client:
            read-idle-timeout: 30m # 30 minutes

6.24.2 Using @ClientWebSocket

The@ClientWebSocket annotation can be used in combination with theWebSocketClient interface to define WebSocket clients.

You can inject a reference to the aWebSocketClient instance using the@Client annotation:

@Inject
@Client("http://localhost:8080")
RxWebSocketClient webSocketClient;

This allows you to use the same service discovery and load balancing features for WebSocket clients.

Once you have a reference to theWebSocketClient interface you can use the connect method to obtain a connected instance of a bean annotated with@ClientWebSocket.

For example consider the following implementation:

WebSocket Chat Example

import io.micronaut.http.HttpRequest;
import io.micronaut.websocket.WebSocketSession;
import io.micronaut.websocket.annotation.ClientWebSocket;
import io.micronaut.websocket.annotation.OnMessage;
import io.micronaut.websocket.annotation.OnOpen;
import io.reactivex.Single;

import java.util.Collection;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Future;


@ClientWebSocket("/chat/{topic}/{username}") <b>(1)</b>
public abstract class ChatClientWebSocket implements AutoCloseable { <b>(2)</b>

    private WebSocketSession session;
    private HttpRequest request;
    private String topic;
    private String username;
    private Collection<String> replies = new ConcurrentLinkedQueue<>();

    @OnOpen
    public void onOpen(String topic, String username, WebSocketSession session, HttpRequest request) { <b>(3)</b>
        this.topic = topic;
        this.username = username;
        this.session = session;
        this.request = request;
    }

    public String getTopic() {
        return topic;
    }

    public String getUsername() {
        return username;
    }

    public Collection<String> getReplies() {
        return replies;
    }

    public WebSocketSession getSession() {
        return session;
    }

    public HttpRequest getRequest() {
        return request;
    }

    @OnMessage
    public void onMessage(
            String message) {
        replies.add(message); <b>(4)</b>
    }

WebSocket Chat Example

import io.micronaut.http.HttpRequest
import io.micronaut.websocket.WebSocketSession
import io.micronaut.websocket.annotation.ClientWebSocket
import io.micronaut.websocket.annotation.OnMessage
import io.micronaut.websocket.annotation.OnOpen
import io.reactivex.Single

import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.Future


@ClientWebSocket("/chat/{topic}/{username}") <b>(1)</b>
abstract class ChatClientWebSocket implements AutoCloseable { <b>(2)</b>

    private WebSocketSession session
    private HttpRequest request
    private String topic
    private String username
    private Collection<String> replies = new ConcurrentLinkedQueue<>()

    @OnOpen
    void onOpen(String topic, String username, WebSocketSession session, HttpRequest request) { <b>(3)</b>
        this.topic = topic
        this.username = username
        this.session = session
        this.request = request
    }

    String getTopic() {
        topic
    }

    String getUsername() {
        username
    }

    Collection<String> getReplies() {
        replies
    }

    WebSocketSession getSession() {
        session
    }

    HttpRequest getRequest() {
        request
    }

    @OnMessage
    void onMessage(
            String message) {
        replies.add(message) <b>(4)</b>
    }

WebSocket Chat Example

import io.micronaut.http.HttpRequest
import io.micronaut.websocket.WebSocketSession
import io.micronaut.websocket.annotation.ClientWebSocket
import io.micronaut.websocket.annotation.OnMessage
import io.micronaut.websocket.annotation.OnOpen
import io.reactivex.Single
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.Future


@ClientWebSocket("/chat/{topic}/{username}") <b>(1)</b>
abstract class ChatClientWebSocket : AutoCloseable { <b>(2)</b>

    var session: WebSocketSession? = null
        private set
    var request: HttpRequest<*>? = null
        private set
    var topic: String? = null
        private set
    var username: String? = null
        private set
    private val replies = ConcurrentLinkedQueue<String>()

    @OnOpen
    fun onOpen(topic: String, username: String, session: WebSocketSession, request: HttpRequest<*>) { <b>(3)</b>
        this.topic = topic
        this.username = username
        this.session = session
        this.request = request
    }

    fun getReplies(): Collection<String> {
        return replies
    }

    @OnMessage
    fun onMessage(
            message: String) {
        replies.add(message) <b>(4)</b>
    }
1 The class is abstract (more on that later) and is annotated with@ClientWebSocket
2 The client must implement AutoCloseable and you should ensure that the connection is closed at some point.
3 You can use the same annotations as on the server, in this case @OnOpen to obtain a reference to the underlying session.
4 The @OnMessage annotation can be used to define the method that receives responses from the server.

You can also define abstract methods that start with either send or broadcast and these methods will be implemented for you at compile time. For example:

WebSocket Send Methods

public abstract void send(String message);

Note by returning void this tells Micronaut that the method is a blocking send. You can instead define methods that return either futures or a Publisher :

WebSocket Send Methods

public abstract io.reactivex.Single<String> send(String message);

The above example defines a send method that returns an Single .

WebSocket Send Methods

public abstract java.util.concurrent.Future<String> sendAsync(String message);

The above example defines a send method that executes asynchronously and returns a Future to access the results.

Once you have defined a client class you can connect to the client socket and start sending messages:

Connecting a Client WebSocket

ChatClientWebSocket chatClient = webSocketClient.connect(ChatClientWebSocket.class, "/chat/football/fred").blockingFirst();
chatClient.send("Hello World!");
For illustration purposes we use blockingFirst() to obtain the client, it is however possible to combine connect (which returns an Flowable ) to perform non-blocking interaction via WebSocket.

Using the CLI

If you have created your project using the Micronaut CLI and the default ( service ) profile, you can use the create-websocket-client command to create an abstract class withWebSocketClient.

$ mn create-websocket-client MyChat
| Rendered template WebsocketClient.java to destination src/main/java/example/MyChatClient.java

6.25 HTTP/2 Support

Since Micronaut 2.x, Micronaut’s Netty-based HTTP server can be configured to support HTTP/2.

Configuring the Server for HTTP/2

The first step to doing so is setting the supported HTTP version is the server configuration:

Enabling HTTP/2 Support

micronaut:
  server:
      http-version: 2.0

With this configuration in place Micronaut will enable support for the h2c protocol (see HTTP/2 over cleartext ) which is fine for development.

Since browsers don’t support h2c and in general HTTP/2 over TLS (the h2 protocol) is recommended for production you should enablesupport. For development this can be done with:

Enabling h2 Protocol Support
micronaut:
  ssl:
      enabled: true
      buildSelfSigned: true
  server:
      http-version: 2.0

For production, see thesection of the documentation.

Note that if you deployment environment is JDK 8 or if you wish for improved support for OpenSSL you will need to define the following dependencies on Netty Tomcat Native:

runtimeOnly("io.netty:netty-tcnative:2.0.29.Final")
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-tcnative</artifactId>
    <version>2.0.29.Final</version>
    <scope>runtime</scope>
</dependency>
runtimeOnly("io.netty:netty-tcnative-boringssl-static:2.0.29.Final")
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-tcnative-boringssl-static</artifactId>
    <version>2.0.29.Final</version>
    <scope>runtime</scope>
</dependency>

In addition to a dependency on the appropriate native library for your architecture. For example:

Configuring Tomcat Native

runtimeOnly "io.netty:netty-tcnative-boringssl-static:2.0.29.Final:${Os.isFamily(Os.FAMILY_MAC) ? 'osx-x86_64' : 'linux-x86_64'}"

See the documentation on Tomcat Native for more information.

HTTP/2 Clients

By default Micronaut’s HTTP client is configured to support HTTP 1.1. To enable support for HTTP/2 you can do so globally by setting the supported HTTP version if configuration:

Enabling HTTP/2 in Clients

micronaut:
    http:
        client:
            http-version: 2.0

Or by specifying the HTTP version to use when injecting the client:

Injecting a HTTP/2 Client

@Inject
@Client(httpVersion=HttpVersion.HTTP_2_0)
RxHttpClient client;

6.26 Server Events

The HTTP server will emit a number of, defined in the io.micronaut.runtime.server.event package, that you can write listeners for. The following table summarizes those events:

Table 1. Server Events
Event Description

ServerStartupEvent

Emitted when the server completes startup

ServerShutdownEvent

Emitted when the server shuts down

ServiceReadyEvent

Emitted after allServerStartupEvent listeners have been executed and exposes the EmbeddedServerInstance

ServiceStoppedEvent

Emitted after allServerShutdownEvent listeners have been executed and exposes the EmbeddedServerInstance

If you do significant work within a listener for aServerStartupEvent this will slow down you startup time.

The following example defines a ApplicationEventListener that listens forServerStartupEvent:

Listening for Server Startup Events

import io.micronaut.context.event.ApplicationEventListener;
...
@Singleton
public class StartupListener implements ApplicationEventListener<ServerStartupEvent> {
    @Override
    public void onApplicationEvent(ServerStartupEvent event) {
        // logic here
        ...
    }
}

Alternatively, you can also use the@EventListener annotation on a method of any existing bean that accepts ServerStartupEvent :

Using @EventListener with ServerStartupEvent
import io.micronaut.runtime.server.event.*;
import io.micronaut.runtime.event.annotation.*;
...
@Singleton
public class MyBean {

    @EventListener
    public void onStartup(ServerStartupEvent event) {
        // logic here
        ...
    }
}

6.27 Configuring the HTTP Server

The HTTP server features a number of configuration options you may wish to tweak. They are defined in the NettyHttpServerConfiguration configuration class, which extends HttpServerConfiguration .

The following example shows how to tweak configuration options for the server via application.yml :

Configuring HTTP server settings

micronaut:
    server:
        maxRequestSize: 1MB
        host: localhost <i data-value="1"></i><b>(1)</b>
        netty:
           maxHeaderSize: 500KB <i data-value="2"></i><b>(2)</b>
           worker:
              threads: 8 <i data-value="3"></i><b>(3)</b>
           childOptions:
              autoRead: true <i data-value="4"></i><b>(4)</b>
1 By default Micronaut will bind to all the network interfaces. Use localhost to bind only to loopback network interface
2 Maximum size for headers
3 Number of netty worker threads
4 Auto read request body
Table 1. Configuration Properties for NettyHttpServerConfiguration
Property Type Description

micronaut.server.netty.child-options

java.util.Map

Sets the Netty child worker options.

micronaut.server.netty.options

java.util.Map

Sets the channel options.

micronaut.server.netty.max-initial-line-length

int

Sets the maximum initial line length for the HTTP request. Default value (4096).

micronaut.server.netty.max-header-size

int

Sets the maximum size of any one header. Default value (8192).

micronaut.server.netty.max-chunk-size

int

Sets the maximum size of any single request chunk. Default value (8192).

micronaut.server.netty.chunked-supported

boolean

Sets whether chunked transfer encoding is supported. Default value (true).

micronaut.server.netty.validate-headers

boolean

Sets whether to validate incoming headers. Default value (true).

micronaut.server.netty.initial-buffer-size

int

Sets the initial buffer size. Default value (128).

micronaut.server.netty.log-level

io.netty.handler.logging.LogLevel

Sets the Netty log level.

micronaut.server.netty.compression-threshold

int

Sets the minimum size of a request body must be in order to be compressed. Default value (1024).

micronaut.server.netty.compression-level

int

Sets the compression level (0-9). Default value (6).

micronaut.server.netty.use-native-transport

boolean

Sets whether to use netty’s native transport (epoll or kqueue) if available . Default value (false).

micronaut.server.netty.fallback-protocol

java.lang.String

Sets the fallback protocol to use when negotiating via ALPN.

Using Native Transports

The native Netty transports add features specific to a particular platform, generate less garbage, and generally improve performance when compared to the NIO based transport.

To enable native transports first add a necessarily dependency:

For macOS:

runtime("io.netty:netty-transport-native-kqueue:osx-x86_64")
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-transport-native-kqueue</artifactId>
    <scope>runtime</scope>
    <classifier>osx-x86_64</classifier>
</dependency>

For Linux:

runtime("io.netty:netty-transport-native-epoll:linux-x86_64")
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-transport-native-epoll</artifactId>
    <scope>runtime</scope>
    <classifier>linux-x86_64</classifier>
</dependency>

Then configure the default event loop group to prefer native transports:

Configuring The Default Event Loop to Prefer Native Transports

micronaut:
    netty:
        event-loops:
            default:
                prefer-native-transport: true

6.27.1 Configuring Server Thread Pools

The HTTP server is built on Netty which is designed as a non-blocking I/O toolkit in an event loop model.

The Netty worker event loop uses the "default" named event loop group. That event loop can be configured through micronaut.server.netty.worker , or micronaut.netty.event-loops.default , with the latter having precedence.

Table 1. Configuration Properties for NettyHttpServerConfiguration$Worker
Property Type Description

micronaut.server.netty.worker.event-loop-group

java.lang.String

Sets the name to use.

micronaut.server.netty.worker.threads

int

Sets the number of threads for the event loop group.

micronaut.server.netty.worker.io-ratio

java.lang.Integer

Sets the I/O ratio.

micronaut.server.netty.worker.executor

java.lang.String

Sets the name of the executor.

micronaut.server.netty.worker.prefer-native-transport

boolean

Set whether to prefer the native transport if available

The parent event loop can be configured with micronaut.server.netty.parent with the same configuration options.

The server can also be configured to use a different named worker event loop:

Using a different event loop for the server

micronaut:
    server:
        netty:
            worker:
                event-loop-group: other
    netty:
        event-loops:
            other:
                num-threads: 10
The default value for the number of threads is the value of the system property io.netty.eventLoopThreads , or if not specified, the available processors x 2.

See the following table for configuring event loops:

Table 2. Configuration Properties for DefaultEventLoopGroupConfiguration
Property Type Description

micronaut.netty.event-loops.*.num-threads

int

micronaut.netty.event-loops.*.io-ratio

java.lang.Integer

micronaut.netty.event-loops.*.prefer-native-transport

boolean

micronaut.netty.event-loops.*.executor

java.lang.String

Blocking Operations

When dealing with blocking operations, Micronaut will shift the blocking operations to an unbound, caching I/O thread pool by default. You can configure the I/O thread pool using the ExecutorConfiguration named io . For example:

Configuring the Server I/O Thread Pool

micronaut:
    executors:
        io:
           type: fixed
           nThreads: 75

The above configuration will create a fixed thread pool with 75 threads.

6.27.2 Configuring the Netty Pipeline

You can customize the Netty pipeline by writing anthat listens for the creation of ChannelPipelineCustomizer .

Both the Netty HTTP server and client implement this interface and it allows you to customize the Netty ChannelPipeline and add additional handlers.

The ChannelPipelineCustomizer interface defines constants for the names of the various handlers Micronaut registers.

As an example the following demonstrates registing the Logbook library which includes additional Netty handlers to perform request and response logging:

Customizing the Netty pipeline for Logbook

import io.micronaut.context.annotation.Requires;
import io.micronaut.context.event.*;
import io.micronaut.http.netty.channel.ChannelPipelineCustomizer;
import org.zalando.logbook.Logbook;
import org.zalando.logbook.netty.*;
import javax.inject.Singleton;

@Requires(beans = Logbook.class)
@Singleton
public class LogbookPipelineCustomizer implements BeanCreatedEventListener<ChannelPipelineCustomizer> { <b>(1)</b>
    private final Logbook logbook;

    public LogbookPipelineCustomizer(Logbook logbook) {
        this.logbook = logbook;
    }

    @Override
    public ChannelPipelineCustomizer onCreated(BeanCreatedEvent<ChannelPipelineCustomizer> event) {
        ChannelPipelineCustomizer customizer = event.getBean();

        if (customizer.isServerChannel()) { <b>(2)</b>
            customizer.doOnConnect(pipeline -> {
                pipeline.addAfter(
                    ChannelPipelineCustomizer.HANDLER_HTTP_SERVER_CODEC,
                    "logbook",
                    new LogbookServerHandler(logbook)
                );
                return pipeline;
            });
        } else { <b>(3)</b>
            customizer.doOnConnect(pipeline -> {
                pipeline.addAfter(
                        ChannelPipelineCustomizer.HANDLER_HTTP_CLIENT_CODEC,
                        "logbook",
                        new LogbookClientHandler(logbook)
                );
                return pipeline;
            });
        }
        return customizer;
    }
}

Customizing the Netty pipeline for Logbook

import io.micronaut.context.annotation.Requires
import io.micronaut.context.event.*
import io.micronaut.http.netty.channel.ChannelPipelineCustomizer
import io.netty.channel.ChannelPipeline
import org.zalando.logbook.Logbook
import org.zalando.logbook.netty.*
import javax.inject.Singleton

@Requires(beans = Logbook.class)
@Singleton
class LogbookPipelineCustomizer implements BeanCreatedEventListener<ChannelPipelineCustomizer> { <b>(1)</b>
    private final Logbook logbook

    LogbookPipelineCustomizer(Logbook logbook) {
        this.logbook = logbook
    }

    @Override
    ChannelPipelineCustomizer onCreated(BeanCreatedEvent<ChannelPipelineCustomizer> event) {
        ChannelPipelineCustomizer customizer = event.getBean()

        if (customizer.isServerChannel()) { <b>(2)</b>
            customizer.doOnConnect( { ChannelPipeline pipeline ->
                pipeline.addAfter(
                        ChannelPipelineCustomizer.HANDLER_HTTP_SERVER_CODEC,
                        "logbook",
                        new LogbookServerHandler(logbook)
                )
                return pipeline
            })
        } else { <b>(3)</b>
            customizer.doOnConnect({ ChannelPipeline pipeline ->
                pipeline.addAfter(
                        ChannelPipelineCustomizer.HANDLER_HTTP_CLIENT_CODEC,
                        "logbook",
                        new LogbookClientHandler(logbook)
                )
                return pipeline
            })
        }
        return customizer
    }
}

Customizing the Netty pipeline for Logbook

import io.micronaut.context.annotation.Requires
import io.micronaut.context.event.*
import io.micronaut.http.netty.channel.ChannelPipelineCustomizer
import io.netty.channel.ChannelPipeline
import org.zalando.logbook.Logbook
import org.zalando.logbook.netty.*
import javax.inject.Singleton

@Requires(beans = [Logbook::class])
@Singleton
class LogbookPipelineCustomizer(private val logbook: Logbook) : BeanCreatedEventListener<ChannelPipelineCustomizer> { <b>(1)</b>

    override fun onCreated(event: BeanCreatedEvent<ChannelPipelineCustomizer>): ChannelPipelineCustomizer {
        val customizer = event.bean
        if (customizer.isServerChannel) { <b>(2)</b>
            customizer.doOnConnect { pipeline: ChannelPipeline ->
                pipeline.addAfter(
                        ChannelPipelineCustomizer.HANDLER_HTTP_SERVER_CODEC,
                        "logbook",
                        LogbookServerHandler(logbook)
                )
                pipeline
            }
        } else { <b>(3)</b>
            customizer.doOnConnect { pipeline: ChannelPipeline ->
                pipeline.addAfter(
                        ChannelPipelineCustomizer.HANDLER_HTTP_CLIENT_CODEC,
                        "logbook",
                        LogbookClientHandler(logbook)
                )
                pipeline
            }
        }
        return customizer
    }
}
1 A bean is created that implements ChannelPipelineCustomizer and requires the definition of a Logbook bean
2 If the bean being created is the server then the server handler is registered
3 if the bean being created is the client then the client handler is registered

6.27.3 Configuring CORS

Micronaut supports CORS ( Cross Origin Resource Sharing ) out of the box. By default, CORS requests will be rejected. To enable processing of CORS requests, modify your configuration. For example with application.yml :

CORS Configuration Example

micronaut:
    server:
        cors:
            enabled: true

By only enabling CORS processing, a "wide open" strategy will be adopted that will allow requests from any origin.

To change the settings for all origins or a specific origin, change the configuration to provide a set of "configurations". By providing any configuration, the default "wide open" configuration is not configured.

CORS Configurations (…​ is a placeholder)

micronaut:
    server:
        cors:
            enabled: true
            configurations:
                all:
                    ...
                web:
                    ...
                mobile:
                    ...

In the above example, three configurations are being provided. Their names ( all , web , mobile ) are not important and have no significance inside Micronaut. They are there purely to be able to easily recognize the intended user of the configuration.

The same configuration properties can be applied to each configuration. See CorsOriginConfiguration for the reference of properties that can be defined. Each configuration supplied will have its values default to the default values of the corresponding fields.

When a CORS request is made, configurations are searched for allowed origins that are an exact match or match the request origin through a regular expression.

Allowed Origins

To allow any origin for a given configuration, simply don’t include the allowedOrigins key in your configuration.

To specify a list of valid origins, set the allowedOrigins key of the configuration to a list of strings. Each value can either be a static value ( http://www.foo.com ) or a regular expression ( ^http(|s)://www\.google\.com$ ).

Any regular expressions are passed to Pattern#compile and compared to the request origin with Matcher#matches .

Example CORS Configuration

micronaut:
    server:
        cors:
            enabled: true
            configurations:
                web:
                    allowedOrigins:
                        - http://foo.com
                        - ^http(|s):\/\/www\.google\.com$

Allowed Methods

To allow any request method for a given configuration, simply don’t include the allowedMethods key in your configuration.

To specify a list of allowed methods, set the allowedMethods key of the configuration to a list of strings.

Example CORS Configuration

micronaut:
    server:
        cors:
            enabled: true
            configurations:
                web:
                    allowedMethods:
                        - POST
                        - PUT

Allowed Headers

To allow any request header for a given configuration, simply don’t include the allowedHeaders key in your configuration.

To specify a list of allowed headers, set the allowedHeaders key of the configuration to a list of strings.

Example CORS Configuration

micronaut:
    server:
        cors:
            enabled: true
            configurations:
                web:
                    allowedHeaders:
                        - Content-Type
                        - Authorization

Exposed Headers

To configure the list of headers that are sent in the response to a CORS request through the Access-Control-Expose-Headers header, include a list of strings for the exposedHeaders key in your configuration. By default no headers are exposed.

Example CORS Configuration

micronaut:
    server:
        cors:
            enabled: true
            configurations:
                web:
                    exposedHeaders:
                        - Content-Type
                        - Authorization

Allow Credentials

Credentials are allowed by default for CORS requests. To disallow credentials, simply set the allowCredentials option to false .

Example CORS Configuration

micronaut:
    server:
        cors:
            enabled: true
            configurations:
                web:
                    allowCredentials: false

Max Age

The default maximum age that preflight requests can be cached is 30 minutes. To change that behavior, specify a value in seconds.

Example CORS Configuration

micronaut:
    server:
        cors:
            enabled: true
            configurations:
                web:
                    maxAge: 3600 # 1 hour

Multiple Header Values

By default when a header has multiple values, multiple headers will be sent each with a single value. It is possible to change the behavior to send a single header with the list of values comma separated by setting a configuration option.

micronaut:
    server:
        cors:
            single-header: true

6.27.4 Securing the Server with HTTPS

Micronaut supports HTTPS out of the box. By default HTTPS is disabled and all requests are served using HTTP. To enable HTTPS support, modify your configuration. For example with application.yml :

HTTPS Configuration Example

micronaut:
    ssl:
        enabled: true
        buildSelfSigned: true <i data-value="1"></i><b>(1)</b>
1 Micronaut will create a self-signed certificate.
By default Micronaut with HTTPS support starts on port 8443 but you can change the port the property micronaut.ssl.port .
Keep in mind that this configuration will generate a warning on the browser.
Micronaut 2.0: a full stack Java framework for modular, testable applications

Using a valid x509 certificate

It is also possible to configure Micronaut to use an existing valid x509 certificate, for example one created with Let’s Encrypt . You will need the server.crt and server.key files and convert them to a PKCS #12 file.

$ openssl pkcs12 -export \
                 -in server.crt \ <i data-value="1"></i><b>(1)</b>
                 -inkey server.key \ <i data-value="2"></i><b>(2)</b>
                 -out server.p12 \ <i data-value="3"></i><b>(3)</b>
                 -name someAlias \ <i data-value="4"></i><b>(4)</b>
                 -CAfile ca.crt -caname root
1 The original server.crt file
2 The original server.key file
3 The server.p12 file that will be created
4 The alias for the certificate

During the creation of the server.p12 file it is necessary to define a password that will be required later when using the certificate in Micronaut.

Now modify your configuration:

HTTPS Configuration Example

micronaut:
    ssl:
        enabled: true
        keyStore:
            path: classpath:server.p12 <i data-value="1"></i><b>(1)</b>
            password: mypassword <i data-value="2"></i><b>(2)</b>
            type: PKCS12
1 The p12 file created. It can also be referenced as file:/path/to/the/file
2 The password defined during the export

With this configuration if we start Micronaut and connect to https://localhost:8443 we still see the warning on the browser but if we inspect the certificate we can check that it’s the one generated by Let’s Encrypt.

Micronaut 2.0: a full stack Java framework for modular, testable applications

Finally we can test that the certificate is valid for the browser just by adding an alias to the domain in /etc/hosts file:

$ cat /etc/hosts
...
127.0.0.1   my-domain.org
...

Now we can connect to https://my-domain.org:8443 :

Micronaut 2.0: a full stack Java framework for modular, testable applications

Using Java Keystore (JKS)

It is not recommended using this type of certificate because it is a proprietary format and it’s better to use a PKCS12 format. In any case Micronaut also supports it.

Convert the p12 certificate to a JKS one:

$ keytool -importkeystore \
          -deststorepass newPassword -destkeypass newPassword \ <i data-value="1"></i><b>(1)</b>
          -destkeystore server.keystore \ <i data-value="2"></i><b>(2)</b>
          -srckeystore server.p12 -srcstoretype PKCS12 -srcstorepass mypassword \ <i data-value="3"></i><b>(3)</b>
          -alias someAlias <i data-value="4"></i><b>(4)</b>
1 It is necessary to define a the password for the keystore
2 The file that will be created
3 The PKCS12 file created before and the password defined during the creation
4 The alias used before
If either srcstorepass or alias are not the same as defined in the p12 file, the conversion will fail.

Now modify your configuration:

HTTPS Configuration Example

micronaut:
    ssl:
        enabled: true
        keyStore:
            path: classpath:server.keystore
            password: newPassword
            type: JKS

Start Micronaut and the application is running on https://localhost:8443 using the certificate in the keystore.

6.27.5 Enabling HTTP and HTTPS

Micronaut supports binding both HTTP and HTTPS out of the box. To enable dual protocol support, modify your configuration. For example with application.yml :

Dual Protocol Configuration Example

micronaut:
    ssl:
        enabled: true
        buildSelfSigned: true <i data-value="1"></i><b>(1)</b>
    server:
        dualProtocol : true <i data-value="2"></i><b>(2)</b>
1 You will need to configure ssl for https to work. In this example we are just using self signed but seeSecuring the Server with HTTPSfor other configurations
2 To enable http & https is an opt-in feature, setting the dualProtocol flag enables that, by default Micronaut will only enable one or the other

6.27.6 Enabling Access Logger

In the spirit of apache mod_log_config and Tomcat Access Log Valve , it is possible to enabled an access logger for the http server (it works for both http/1 and http/2.)

To enable and configure the access logger, in application.yml set:

Enabling the access logger

micronaut:
    server:
        netty:
           access-logger:
              enabled: true # Enables the access logger
              logger-name: my-access-logger # A logger name, optional, default is `HTTP_ACCESS_LOGGER`
              log-format: common # A log format, optional, default is Common Log Format

Logback Configuration

In addition to enabling the access logger, you have to add a logger for the specified or default logger name. For instance using the default logger name for logback:

Logback configuration

<appender
    name="httpAccessLogAppender"
   >
    <append>true</append>
    <file>log/http-access.log</file>
    <rollingPolicy>
        <!-- daily rollover -->
        <fileNamePattern>log/http-access-%d{yyyy-MM-dd}.log
        </fileNamePattern>
        <maxHistory>7</maxHistory>
    </rollingPolicy>
    <encoder>
        <charset>UTF-8</charset>
        <pattern>%msg%n</pattern>
    </encoder>
        <immediateFlush>true</immediateFlush>
</appender>

<logger name="HTTP_ACCESS_LOGGER" additivity="false" level="info">
    <appender-ref ref="httpAccessLogAppender" />
</logger>

The pattern should only have the message marker as other elements will be processed by the access logger.

Log Format

The syntax is based on Apache httpd log format .

Here are the supported markers:

  • %a- Remote IP address

  • %A- Local IP address

  • %b- Bytes sent, excluding HTTP headers, or '-' if no bytes were sent

  • %B- Bytes sent, excluding HTTP headers

  • %h- Remote host name

  • %H- Request protocol

  • %{<header>}i- Request header. If the argument is omitted ( %i ) all headers will be printed

  • %{<header>}o- Response header. If the argument is omitted ( %o ) all headers will be printed

  • %{<cookie>}C- Request cookie (COOKIE). If the argument is omitted ( %C ) all cookies will be printed

  • %{<cookie>}c- Response cookie (SET_COOKIE). If the argument is omitted ( %c ) all cookies will be printed

  • %l- Remote logical username from identd (always returns '-')

  • %m- Request method

  • %p- Local port

  • %q- Query string (excluding the '?' character)

  • %r- First line of the request

  • %s- HTTP status code of the response

  • %{<format>}t- Date and time. If the argument is ommitted the Common Log Format format is used ("'['dd/MMM/yyyy:HH:mm:ss Z']'").

    • If the format starts with begin: (default) the time is taken at the beginning of the request processing. If it starts with end: it is the time when the log entry gets written, close to the end of the request processing.

    • The format should follow the DateTimeFormatter syntax.

  • %{property}u- Remote user that was authenticated. When micronaut-session is on the classpath, returns the session id if the argument is omitted or the specified property otherwise prints '-'

  • %U- Requested URI

  • %v- Local server name

  • %D- Time taken to process the request, in millis

  • %T- Time taken to process the request, in seconds

In addition, you can use the following aliases for commonly utilized patterns:

6.28 Server Side View Rendering

Micronaut supports Server Side View Rendering.

See the documentation for Micronaut Views for more information.

6.29 OpenAPI / Swagger Support

To configure Micronaut integration with OpenAPI/Swagger , please follow these instructions

6.30 GraphQL Support

GraphQL is a query language for building APIs that provides an intuitive and flexible syntax and a system for describing data requirements and interactions.

See the documentation for Micronaut GraphQL for more information on how to build GraphQL applications with Micronaut.

7 The HTTP Client

Using the CLI

If you are creating your project using the Micronaut CLI, the http-client dependency is included by default.

A critical component of any Microservice architecture is the client communication between Microservices. With that in mind Micronaut features a built in HTTP client that has both a low-level API and a higher level AOP-driven API.

Regardless whether you choose to use Micronaut’s HTTP server, you may wish to use the Micronaut HTTP client in your application since it is a feature-rich client implementation.

To use the HTTP client you must have the http-client dependency on your classpath.

implementation("io.micronaut:micronaut-http-client")
<dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-http-client</artifactId>
</dependency>

Since the higher level API is built on the low-level HTTP client, we will first introduce the low-level client.

7.1 Using the Low-Level HTTP Client

TheHttpClient interface forms the basis for the low-level API. This interfaces declares methods to help ease executing HTTP requests and receive responses.

The majority of the methods in theHttpClient interface returns Reactive Streams Publisher instances, which is not always the most useful interface to work against, hence a sub-interface calledRxHttpClient is included that provides a variation of theHttpClient interface that returns RxJava Flowable types.

7.1.1 Sending your first HTTP request

Obtaining a HttpClient

There are a few ways by which you can obtain a reference to aHttpClient. The most common way is using theClient annotation. For example:

Injecting an HTTP client

@Client("https://api.twitter.com/1.1") @Inject RxHttpClient httpClient;

The above example will inject a client that targets the Twitter API.

@field:Client("\${myapp.api.twitter.url}") @Inject lateinit var httpClient: RxHttpClient

The above Kotlin example will inject a client that targets the Twitter API using a configuration path. Note the required escaping (backslash) on "\${path.to.config}" which is required due to Kotlin string interpolation.

TheClient annotation is also a custom scope that will manage the creation ofHttpClient instances and ensure they are shutdown when the application shuts down.

The value you pass to theClient annotation can be one of the following:

  • A absolute URI. Example https://api.twitter.com/1.1

  • A relative URI, in which case the server targeted will be the current server (useful for testing)

  • A service identifier. See the section onfor more information on this topic.

Another way to create an HttpClient is with the create static method of theRxHttpClient, however this approach is not recommended as you will have to ensure you manually shutdown the client and of course no dependency injection will occur for the created client.

Performing an HTTP GET

Generally there are two methods of interest when working with the HttpClient . The first method is called retrieve , which will execute an HTTP request and return the body in whichever type you request (by default a String ) as an RxJava Flowable .

The retrieve method accepts anHttpRequest object or a String URI to the endpoint you wish to request.

The following example shows how to use retrieve to execute an HTTP GET and receive the response body as a String :

Using retrieve

String uri = UriBuilder.of("/hello/{name}")
                               .expand(Collections.singletonMap("name", "John"))
                               .toString();
        assertEquals("/hello/John", uri);

        String result = client.toBlocking().retrieve(uri);

        assertEquals(
                "Hello John",
                result
        );

Using retrieve

when:
        String uri = UriBuilder.of("/hello/{name}")
                               .expand(Collections.singletonMap("name", "John"))
                               .toString()
        then:
        "/hello/John" == uri

        when:
        String result = client.toBlocking().retrieve(uri)

        then:
        "Hello John" == result

Using retrieve

val uri = UriBuilder.of("/hello/{name}")
                    .expand(Collections.singletonMap("name", "John"))
                    .toString()
            uri shouldBe "/hello/John"

            val result = client.toBlocking().retrieve(uri)

            result shouldBe "Hello John"

Note that in this example, for illustration purposes we are calling toBlocking() to return a blocking version of the client. However, in production code you should not do this and instead rely on the non-blocking nature of the Micronaut HTTP server.

For example the following @Controller method calls another endpoint in a non-blocking manner:

Using the HTTP client without blocking

import io.micronaut.http.HttpStatus;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.*;
import io.micronaut.http.client.RxHttpClient;
import io.micronaut.http.client.annotation.Client;
import io.reactivex.Maybe;

import static io.micronaut.http.HttpRequest.GET;

    @Get("/hello/{name}")
    Maybe<String> hello(String name) { <b>(1)</b>
        return httpClient.retrieve( GET("/hello/" + name) )
                         .firstElement(); <b>(2)</b>
    }

Using the HTTP client without blocking

import io.micronaut.http.HttpStatus
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.*
import io.micronaut.http.client.RxHttpClient
import io.micronaut.http.client.annotation.Client
import io.reactivex.Maybe

import static io.micronaut.http.HttpRequest.GET

    @Get("/hello/{name}")
    Maybe<String> hello(String name) { <b>(1)</b>
        return httpClient.retrieve( GET("/hello/" + name) )
                         .firstElement() <b>(2)</b>
    }

Using the HTTP client without blocking

import io.micronaut.http.HttpStatus
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.*
import io.micronaut.http.client.RxHttpClient
import io.micronaut.http.client.annotation.Client
import io.reactivex.Maybe

import io.micronaut.http.HttpRequest.GET

    @Get("/hello/{name}")
    internal fun hello(name: String): Maybe<String> { <b>(1)</b>
        return httpClient.retrieve(GET<Any>("/hello/$name"))
                .firstElement() <b>(2)</b>
    }
1 The method hello returns a Maybe which may or may not emit an item. If an item is not emitted a 404 is returned.
2 The retrieve method is called which returns a Flowable which has a firstElement method that returns the first emitted item or nothing
Using RxJava (or Reactor if you prefer) you can easily and efficiently compose multiple HTTP client calls without blocking (which will limit the throughput and scalability of your application).

Debugging / Tracing the HTTP Client

To debug the requests being sent and received from the HTTP client you can enable tracing logging via your logback.xml file:

logback.xml

<logger name="io.micronaut.http.client" level="TRACE"/>

Client Specific Debugging / Tracing

To enable client-specific logging you could configure the default logger for all HTTP clients. And, you could also configure different loggers for different clients usingClient Specific Configuration. For example, in application.yml :

application.yml

micronaut:
    http:
        client:
            logger-name: mylogger
        services:
            otherClient:
                logger-name: other.client

And, then enable logging in logback.yml :

logback.xml

<logger name="mylogger" level="DEBUG"/>
<logger name="other.client" level="TRACE"/>

Customizing the HTTP Request

The previous example demonstrated using the static methods of theHttpRequest interface to construct aMutableHttpRequest instance. Like the name suggests a MutableHttpRequest can be mutated including the ability to add headers, customize the request body and so on. For example:

Passing an HttpRequest to retrieve

Flowable<String> response = client.retrieve(
                GET("/hello/John")
                .header("X-My-Header", "SomeValue")
        );

Passing an HttpRequest to retrieve

Flowable<String> response = client.retrieve(
                GET("/hello/John")
                .header("X-My-Header", "SomeValue")
        )

Passing an HttpRequest to retrieve

val response = client.retrieve(
                    GET<Any>("/hello/John")
                            .header("X-My-Header", "SomeValue")
            )

The above example adds an additional header called X-My-Header to the request before it is sent. TheMutableHttpRequest interface has a bunch more convenience methods that make it easy to modify the request in common ways.

Reading JSON Responses

Typically with Microservices a message encoding format is used such as JSON. Micronaut’s HTTP client leverages Jackson for JSON parsing hence whatever type Jackson can decode can passed as a second argument to retrieve .

For example consider the following @Controller method that returns a JSON response:

Returning JSON from a controller

@Get("/greet/{name}")
    Message greet(String name) {
        return new Message("Hello " + name);
    }

Returning JSON from a controller

@Get("/greet/{name}")
    Message greet(String name) {
        return new Message("Hello " + name)
    }

Returning JSON from a controller

@Get("/greet/{name}")
    internal fun greet(name: String): Message {
        return Message("Hello $name")
    }

The method above returns a POJO of type Message which looks like:

Message POJO

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

public class Message {
    private final String text;

    @JsonCreator
    public Message(@JsonProperty("text") String text) {
        this.text = text;
    }

    public String getText() {
        return text;
    }
}

Message POJO

import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonProperty

class Message {
    private final String text

    @JsonCreator
    Message(@JsonProperty("text") String text) {
        this.text = text
    }

    String getText() {
        return text
    }
}

Message POJO

import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonProperty


class Message @JsonCreator
constructor(@param:JsonProperty("text") val text: String)
Jackson annotations are used to map the constructor

On the client end you can call this endpoint and decode the JSON into a map using the retrieve method as follows:

Decoding the response body to a Map

Flowable<Map> response = client.retrieve(
                GET("/greet/John"), Map.class
        );

Decoding the response body to a Map

Flowable<Map> response = client.retrieve(
                GET("/greet/John"), Map.class
        )

Decoding the response body to a Map

var response: Flowable<Map<*, *>> = client.retrieve(
                    GET<Any>("/greet/John"), Map::class.java
            )

The above examples decodes the response into a Map , representing the JSON. If you wish to customize the type of the key and string you can use the Argument.of(..) method:

Decoding the response body to a Map

response = client.retrieve(
                GET("/greet/John"),
                Argument.of(Map.class, String.class, String.class) <b>(1)</b>
        );

Decoding the response body to a Map

response = client.retrieve(
                GET("/greet/John"),
                Argument.of(Map.class, String.class, String.class) <b>(1)</b>
        )

Decoding the response body to a Map

response = client.retrieve(
                    GET<Any>("/greet/John"),
                    Argument.of(Map::class.java, String::class.java, String::class.java) <b>(1)</b>
            )
1 The Argument.of method is used to return a Map whether the key and value are typed as String

Whilst retrieving JSON as a map can be desirable, more often than not you will want to decode objects into Plain Old Java Objects (POJOs). To do that simply pass the type instead:

Decoding the response body to a POJO

Flowable<Message> response = client.retrieve(
                GET("/greet/John"), Message.class
        );

        assertEquals(
                "Hello John",
                response.blockingFirst().getText()
        );

Decoding the response body to a POJO

when:
        Flowable<Message> response = client.retrieve(
                GET("/greet/John"), Message.class
        )

        then:
        "Hello John" == response.blockingFirst().getText()

Decoding the response body to a POJO

val response = client.retrieve(
                    GET<Any>("/greet/John"), Message::class.java
            )

            response.blockingFirst().text shouldBe "Hello John"

Note how you can use the same Java type on both the client and the server. The implication of this is that typically you will want to define a common API project where you define the interfaces and types that define your API.

Decoding Other Content Types

If the server you are communicating with uses a custom content type that is not JSON by default Micronaut’s HTTP client will not know how to decode this type.

To resolve this issue you can registerMediaTypeCodec as a bean and it will be automatically picked up and used to decode (or encode) messages.

Receiving the Full HTTP Response

Sometimes, receiving just the object is not enough and you need information about the response. In this case, instead of retrieve you should use the exchange method:

Recieving the Full HTTP Response

Flowable<HttpResponse<Message>> call = client.exchange(
                GET("/greet/John"), Message.class <b>(1)</b>
        );

        HttpResponse<Message> response = call.blockingFirst();
        Optional<Message> message = response.getBody(Message.class); <b>(2)</b>
        // check the status
        assertEquals(
                HttpStatus.OK,
                response.getStatus() <b>(3)</b>
        );
        // check the body
        assertTrue(message.isPresent());
        assertEquals(
                "Hello John",
                message.get().getText()
        );

Recieving the Full HTTP Response

when:
        Flowable<HttpResponse<Message>> call = client.exchange(
                GET("/greet/John"), Message.class <b>(1)</b>
        )

        HttpResponse<Message> response = call.blockingFirst();
        Optional<Message> message = response.getBody(Message.class) <b>(2)</b>
        // check the status
        then:
        HttpStatus.OK == response.getStatus() <b>(3)</b>
        // check the body
        message.isPresent()
        "Hello John" == message.get().getText()

Recieving the Full HTTP Response

val call = client.exchange(
                    GET<Any>("/greet/John"), Message::class.java <b>(1)</b>
            )

            val response = call.blockingFirst()
            val message = response.getBody(Message::class.java) <b>(2)</b>
            // check the status
            response.status shouldBe HttpStatus.OK <b>(3)</b>
            // check the body
            message.isPresent shouldBe true
            message.get().text shouldBe "Hello John"
1 The exchange method is used to receive theHttpResponse
2 The body can be retrieved using the getBody(..) method of the response
3 Other aspects of the response, such as theHttpStatus can be checked

The above example receives the fullHttpResponse object from which you can obtain headers and other useful information.

7.1.2 Posting a Request Body

All the examples up until now have used the same HTTP method i.e GET . TheHttpRequest interface has factory methods for all the different HTTP methods. The following table summarizes the available methods:

Table 1. HttpRequest Factory Methods
Method Description Allows Body

HttpRequest.GET(java.lang.String)

Constructs an HTTP GET request

false

HttpRequest.OPTIONS(java.lang.String)

Constructs an HTTP OPTIONS request

false

HttpRequest.HEAD(java.lang.String)

Constructs an HTTP HEAD request

false

HttpRequest.POST(java.lang.String,T)

Constructs an HTTP POST request

true

HttpRequest.PUT(java.lang.String,T)

Constructs an HTTP PUT request

true

HttpRequest.PATCH(java.lang.String,T)

Constructs an HTTP PATCH request

true

HttpRequest.DELETE(java.lang.String)

Constructs an HTTP DELETE request

true

A create method also exists to construct a request for anyHttpMethod type. Since the POST , PUT and PATCH methods require a body, a second argument which is the body object is required.

The following example demonstrates how to send a simply String body:

Sending a String body
Flowable<HttpResponse<String>> call = client.exchange(
                POST("/hello", "Hello John") <b>(1)</b>
                    .contentType(MediaType.TEXT_PLAIN_TYPE)
                    .accept(MediaType.TEXT_PLAIN_TYPE), <b>(2)</b>
                String.class <b>(3)</b>
        );
Sending a String body
Flowable<HttpResponse<String>> call = client.exchange(
                POST("/hello", "Hello John") <b>(1)</b>
                    .contentType(MediaType.TEXT_PLAIN_TYPE)
                    .accept(MediaType.TEXT_PLAIN_TYPE), <b>(2)</b>
                String.class <b>(3)</b>
        )
Sending a String body
val call = client.exchange(
                    POST("/hello", "Hello John") <b>(1)</b>
                            .contentType(MediaType.TEXT_PLAIN_TYPE)
                            .accept(MediaType.TEXT_PLAIN_TYPE), String::class.java <b>(3)</b>
            )
1 The POST method is used with the first argument being the URI and the second argument the body
2 The content type and accepted type are set to text/plain (the default content type is application/json )
3 The expected response type is a String

Sending JSON

The previous example sends plain text, if you wish send JSON you can simply pass the object you wish to encode as JSON, whether that be a map or a POJO. As long as Jackson is able to encode it.

For example, the Message class from the previous section, you can create an instance and pass it to the POST method:

Sending a JSON body

Flowable<HttpResponse<Message>> call = client.exchange(
                POST("/greet", new Message("Hello John")), <b>(1)</b>
                Message.class <b>(2)</b>
        );

Sending a JSON body

Flowable<HttpResponse<Message>> call = client.exchange(
                POST("/greet", new Message("Hello John")), <b>(1)</b>
                Message.class <b>(2)</b>
        )

Sending a JSON body

val call = client.exchange(
                    POST("/greet", Message("Hello John")), Message::class.java <b>(2)</b>
            )
1 And instance of Message is created and passed to the POST method
2 The same class is used to decode the response

With the above example the following JSON will be sent as the body of the request:

Resulting JSON

{"text":"Hello John"}

The JSON itself can be customized however you want using Jackson Annotations .

Using a URI Template

If some of the properties of the object need to be in the URI being posted to you can use a URI template.

For example imagine you have a class Book that has a property called title . You can represent the title property in the URI template and then populate it from an instance of Book . For example:

Sending a JSON body with a URI template

Flowable<HttpResponse<Book>> call = client.exchange(
                POST("/amazon/book/{title}", new Book("The Stand")),
                Book.class
        );

Sending a JSON body with a URI template

Flowable<HttpResponse<Book>> call = client.exchange(
                POST("/amazon/book/{title}", new Book("The Stand")),
                Book.class
        );

Sending a JSON body with a URI template

val call = client.exchange(
                    POST("/amazon/book/{title}", Book("The Stand")),
                    Book::class.java
            )

In the above case the title property of the passed object will be included in the URI being posted to.

Sending Form Data

You can also encode a POJO or a map as regular form data instead of JSON. Just set the content type to application/x-www-form-urlencoded on the post request:

Sending a Form Data

Flowable<HttpResponse<Book>> call = client.exchange(
                POST("/amazon/book/{title}", new Book("The Stand"))
                .contentType(MediaType.APPLICATION_FORM_URLENCODED),
                Book.class
        );

Sending a Form Data

Flowable<HttpResponse<Book>> call = client.exchange(
                POST("/amazon/book/{title}", new Book("The Stand"))
                .contentType(MediaType.APPLICATION_FORM_URLENCODED),
                Book.class
        )

Sending a Form Data

val call = client.exchange(
                    POST("/amazon/book/{title}", Book("The Stand"))
                            .contentType(MediaType.APPLICATION_FORM_URLENCODED),
                    Book::class.java
            )

Note that Jackson is used to bind form data too, so to customize the binding process you can use Jackson annotations .

7.1.3 Multipart Client Uploads

The Micronaut HTTP Client supports the ability to create multipart requests. In order to build a multipart request you must set the content type to multipart/form-data and set the body to be an instance ofMultipartBody:

For example:

Creating the body

import io.micronaut.http.client.multipart.MultipartBody;

            String toWrite = "test file";
            File file = File.createTempFile("data", ".txt");
            FileWriter writer = new FileWriter(file);
            writer.write(toWrite);
            writer.close();

            MultipartBody requestBody = MultipartBody.builder()     <b>(1)</b>
                    .addPart(                                       <b>(2)</b>
                        "data",
                        file.getName(),
                        MediaType.TEXT_PLAIN_TYPE,
                        file
                    ).build()   ;                                    <b>(3)</b>

Creating the body

import io.micronaut.http.multipart.CompletedFileUpload
import io.micronaut.http.multipart.StreamingFileUpload
import io.micronaut.http.client.multipart.MultipartBody
import org.reactivestreams.Publisher

        File file = new File(uploadDir, "data.txt")
        file.text = "test file"
        file.createNewFile()


        MultipartBody requestBody = MultipartBody.builder()     <b>(1)</b>
                .addPart(                                       <b>(2)</b>
                    "data",
                    file.name,
                    MediaType.TEXT_PLAIN_TYPE,
                    file
                ).build()                                       <b>(3)</b>

Creating the body

import io.micronaut.http.client.multipart.MultipartBody

            val toWrite = "test file"
            val file = File.createTempFile("data", ".txt")
            val writer = FileWriter(file)
            writer.write(toWrite)
            writer.close()

            val requestBody = MultipartBody.builder()     <b>(1)</b>
                    .addPart(                             <b>(2)</b>
                            "data",
                            file.name,
                            MediaType.TEXT_PLAIN_TYPE,
                            file
                    ).build()                             <b>(3)</b>
1 You need to create a MultipartBody builder for adding parts to the body.
2 Method to add a part to the body, in this case a file. There are different variations of this method which you can see in MultipartBody.Builder .
3 Call the build method to assemble all parts from the builder into a MultipartBody. At least one part is required.

Creating a request

HttpRequest.POST("/multipart/upload", requestBody)       <b>(1)</b>
                            .contentType(MediaType.MULTIPART_FORM_DATA_TYPE) <b>(2)</b>

Creating a request

HttpRequest.POST("/multipart/upload", requestBody)       <b>(1)</b>
                        .contentType(MediaType.MULTIPART_FORM_DATA_TYPE) <b>(2)</b>

Creating a request

HttpRequest.POST("/multipart/upload", requestBody)   <b>(1)</b>
                            .contentType(MediaType.MULTIPART_FORM_DATA_TYPE) <b>(2)</b>
1 The multipart request body with different sets of data.
2 Set the content-type header of the request to multipart/form-data .

7.1.4 Streaming JSON over HTTP

Micronaut’s HTTP client includes support for streaming data over HTTP via the RxStreamingHttpClient interface which includes methods specific to HTTP streaming including:

Table 1. HTTP Streaming Methods
Method Description

dataStream(HttpRequest<I> request)

Returns a stream of data as a Flowable ofByteBuffer

exchangeStream(HttpRequest<I> request)

Returns theHttpResponse wrapping a Flowable ofByteBuffer

jsonStream(HttpRequest<I> request)

Returns a non-blocking stream of JSON objects

In order to do JSON streaming you should on the server side declare a controller method that returns a application/x-json-stream of JSON objects. For example:

Streaming JSON on the Server

import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.reactivex.Flowable;

import java.time.ZonedDateTime;
import java.util.concurrent.TimeUnit;

    @Get(value = "/headlines", processes = MediaType.APPLICATION_JSON_STREAM) <b>(1)</b>
    Flowable<Headline> streamHeadlines() {
        return Flowable.fromCallable(() -> {  <b>(2)</b>
            Headline headline = new Headline();
            headline.setText("Latest Headline at " + ZonedDateTime.now());
            return headline;
        }).repeat(100) <b>(3)</b>
          .delay(1, TimeUnit.SECONDS); <b>(4)</b>
    }

Streaming JSON on the Server

import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.reactivex.Flowable

import java.time.ZonedDateTime
import java.util.concurrent.TimeUnit

    @Get(value = "/headlines", processes = MediaType.APPLICATION_JSON_STREAM) <b>(1)</b>
    Flowable<Headline> streamHeadlines() {
        Flowable.fromCallable({ <b>(2)</b>
            Headline headline = new Headline()
            headline.setText("Latest Headline at " + ZonedDateTime.now())
            return headline
        }).repeat(100) <b>(3)</b>
          .delay(1, TimeUnit.SECONDS) <b>(4)</b>
    }

Streaming JSON on the Server

import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.reactivex.Flowable

import java.time.ZonedDateTime
import java.util.concurrent.TimeUnit


    @Get(value = "/headlines", processes = [MediaType.APPLICATION_JSON_STREAM]) <b>(1)</b>
    internal fun streamHeadlines(): Flowable<Headline> {
        return Flowable.fromCallable {
            <b>(2)</b>
            val headline = Headline()
            headline.text = "Latest Headline at " + ZonedDateTime.now()
            headline
        }.repeat(100) <b>(3)</b>
                .delay(1, TimeUnit.SECONDS) <b>(4)</b>
    }
1 A method streamHeadlines is defined that produces application/x-json-stream
2 A Flowable is created from a Callable function (note no blocking occurs within the function so this is ok, otherwise you would want to subscribeOn an I/O thread pool).
3 The Flowable is set to repeat 100 times
4 The Flowable will emit items with a delay of 1 second between each item
The server does not have to be written in Micronaut, any server that supports JSON streaming will do.

Then on the client simply subscribe to the stream using jsonStream and every time the server emits a JSON object the client will decode and consume it:

Streaming JSON on the Client

Flowable<Headline> headlineStream = client.jsonStream(GET("/streaming/headlines"), Headline.class); <b>(1)</b>
        CompletableFuture<Headline> future = new CompletableFuture<>(); <b>(2)</b>
        headlineStream.subscribe(new Subscriber<Headline>() {
            @Override
            public void onSubscribe(Subscription s) {
                s.request(1); <b>(3)</b>
            }

            @Override
            public void onNext(Headline headline) {
                System.out.println("Received Headline = " + headline.getText());
                future.complete(headline); <b>(4)</b>
            }

            @Override
            public void onError(Throwable t) {
                future.completeExceptionally(t); <b>(5)</b>
            }

            @Override
            public void onComplete() {
                // no-op <b>(6)</b>
            }
        });

Streaming JSON on the Client

Flowable<Headline> headlineStream = client.jsonStream(GET("/streaming/headlines"), Headline.class) <b>(1)</b>
        CompletableFuture<Headline> future = new CompletableFuture<>() <b>(2)</b>
        headlineStream.subscribe(new Subscriber<Headline>() {
            @Override
            void onSubscribe(Subscription s) {
                s.request(1) <b>(3)</b>
            }

            @Override
            void onNext(Headline headline) {
                System.out.println("Received Headline = " + headline.getText())
                future.complete(headline) <b>(4)</b>
            }

            @Override
            void onError(Throwable t) {
                future.completeExceptionally(t) <b>(5)</b>
            }

            @Override
            void onComplete() {
                // no-op <b>(6)</b>
            }
        })

Streaming JSON on the Client

val headlineStream = client.jsonStream(GET<Any>("/streaming/headlines"), Headline::class.java) <b>(1)</b>
            val future = CompletableFuture<Headline>() <b>(2)</b>
            headlineStream.subscribe(object : Subscriber<Headline> {
                override fun onSubscribe(s: Subscription) {
                    s.request(1) <b>(3)</b>
                }

                override fun onNext(headline: Headline) {
                    println("Received Headline = " + headline.text!!)
                    future.complete(headline) <b>(4)</b>
                }

                override fun onError(t: Throwable) {
                    future.completeExceptionally(t) <b>(5)</b>
                }

                override fun onComplete() {
                    // no-op <b>(6)</b>
                }
            })
1 The jsonStream method is used return a Flowable
2 A CompletableFuture is used in the example to receive a value, but what you do with each emitted item is application specific
3 The Subscription is used to request a single item. You can use the Subscription to regulate back pressure and demand.
4 The onNext method is called when an item is emitted
5 The onError method is called when an error occurs
6 The onComplete method is called when all Headline instances have been emitted

Note neither the server or the client in the example above perform blocking I/O at any point.

7.1.5 Configuring HTTP clients

Global Configuration for All Clients

The default HTTP client configuration is aConfiguration Propertiescalled DefaultHttpClientConfiguration that allows configuring the default behaviour for all HTTP clients. For example, in application.yml :

Altering default HTTP client configuration

micronaut:
    http:
        client:
            read-timeout: 5s

The above example sets of readTimeout property of the HttpClientConfiguration class.

Client Specific Configuration

If you wish to have a separate configuration per client then there a couple of options. You can configuremanually in application.yml and apply per-client configuration:

Manually configuring HTTP services

micronaut:
    http:
        services:
            foo:
                urls:
                    - http://foo1
                    - http://foo2
                read-timeout: 5s <i data-value="1"></i><b>(1)</b>
1 The read timeout is applied to the foo client.

WARN: This client configuration can be used in conjunction with the @Client annotation, either by injecting an HttpClient directly or use on a client interface. In any case, all other attributes on the annotation will be ignored other than the service id.

Then simply inject the named client configuration:

Injecting an HTTP client

@Client("foo") @Inject RxHttpClient httpClient;

You can also simply define a bean that extends from HttpClientConfiguration and ensuring that the javax.inject.Named annotation is used to name it appropriately:

Defining an HTTP client configuration bean

@Named("twitter")
@Singleton
class TwitterHttpClientConfiguration extends HttpClientConfiguration {
   public TwitterHttpClientConfiguration(ApplicationConfiguration applicationConfiguration) {
        super(applicationConfiguration);
    }
}

This configuration will then be picked up if you inject a service called twitter using @Client using:

Injecting an HTTP client

@Client("twitter") @Inject RxHttpClient httpClient;

Alternatively if you are not using service discovery then you can use the configuration member of @Client to refer to a specific type:

Injecting an HTTP client

@Client(value="https://api.twitter.com/1.1",
        configuration=TwitterHttpClientConfiguration.class)
@Inject
RxHttpClient httpClient;

Using HTTP Client Connection Pooling

If you have a client that needs to handle a significant number of requests then you can benefit from enabling HTTP client connection pooling. The following configuration will enable pooling for the foo client:

Manually configuring HTTP services

micronaut:
    http:
        services:
            foo:
                urls:
                    - http://foo1
                    - http://foo2
                pool:
                    enabled: true <i data-value="1"></i><b>(1)</b>
                    max-connections: 50 <i data-value="2"></i><b>(2)</b>
1 Enables the pool
2 Sets the maximum number of connections in the pool

See the API for ConnectionPoolConfiguration for details on available options to configure the pool.

Configuring Event Loop Groups

By default Micronaut will share a common Netty EventLoopGroup for worker threads and all HTTP clients threads.

This common EventLoopGroup can be configured via the micronaut.netty.event-loops.default property:

Configuring The Default Event Loop

micronaut:
    netty:
        event-loops:
            default:
                num-threads: 10
                prefer-native-transport: true

You can also use the micronaut.netty.event-loops setting to configure one or more additional event loops. The following table summarizes the properties:

Table 1. Configuration Properties for DefaultEventLoopGroupConfiguration
Property Type Description

micronaut.netty.event-loops.*.num-threads

int

micronaut.netty.event-loops.*.io-ratio

java.lang.Integer

micronaut.netty.event-loops.*.prefer-native-transport

boolean

micronaut.netty.event-loops.*.executor

java.lang.String

For example, if your interactions with an HTTP client involve CPU intensive work it may be worthwhile configuring a separate EventLoopGroup for one or all clients.

The following example configures an additional event loop group called "other" that uses 10 threads:

Configuring Additional Event Loops

micronaut:
    netty:
        event-loops:
            other:
                num-threads: 10
                prefer-native-transport: true

Once an additional event loop has been configured you can alter the HTTP client configuration to use the newly defined event loop group:

Altering the Event Loop Group used by Clients

micronaut:
    http:
        client:
            event-loop-group: other

7.1.6 Error Responses

If an HTTP response is returned with a code of 400 or above, an HttpClientResponseException is created. The exception contains the original response. How that exception gets thrown depends on the return type of the method.

For blocking clients, the exception is thrown and should be caught and handled by the caller. For reactive clients, the exception is passed through the publisher as an error.

7.1.7 Bind Errors

Often you want to consume an endpoint and bind to a POJO if the request is successful or bind to a different POJO if an error occurs. The following example shows how to invoke exchange with a success and error type.

@Controller("/books")
public class BooksController {

    @Get("/{isbn}")
    public HttpResponse find(String isbn) {
        if (isbn.equals("1680502395")) {
            Map<String, Object> m = new HashMap<>();
            m.put("status", 401);
            m.put("error", "Unauthorized");
            m.put("message", "No message available");
            m.put("path", "/books/"+isbn);
            return HttpResponse.status(HttpStatus.UNAUTHORIZED).body(m);

        }
        return HttpResponse.ok(new Book("1491950358", "Building Microservices"));
    }
}
@Controller("/books")
class BooksController {

    @Get("/{isbn}")
    HttpResponse find(String isbn) {
        if (isbn == "1680502395") {
            Map<String, Object> m = new HashMap<>()
            m.put("status", 401)
            m.put("error", "Unauthorized")
            m.put("message", "No message available")
            m.put("path", "/books/"+isbn)
            return HttpResponse.status(HttpStatus.UNAUTHORIZED).body(m)

        }
        return HttpResponse.ok(new Book("1491950358", "Building Microservices"))
    }
}
@Controller("/books")
class BooksController {

    @Get("/{isbn}")
    fun find(isbn: String): HttpResponse<*> {
        if (isbn == "1680502395") {
            val m = HashMap<String, Any>()
            m["status"] = 401
            m["error"] = "Unauthorized"
            m["message"] = "No message available"
            m["path"] = "/books/$isbn"
            return HttpResponse.status<Any>(HttpStatus.UNAUTHORIZED).body(m)

        }
        return HttpResponse.ok(Book("1491950358", "Building Microservices"))
    }
}
@Test
    public void afterAnHttpClientExceptionTheResponseBodyCanBeBoundToAPOJO() {
        try {
            client.toBlocking().exchange(HttpRequest.GET("/books/1680502395"),
                    Argument.of(Book.class), <b>(1)</b>
                    Argument.of(CustomError.class)); <b>(2)</b>
        } catch (HttpClientResponseException e) {
            assertEquals(HttpStatus.UNAUTHORIZED, e.getResponse().getStatus());
            Optional<CustomError> jsonError = e.getResponse().getBody(CustomError.class);
            assertTrue(jsonError.isPresent());
            assertEquals(401, jsonError.get().status);
            assertEquals("Unauthorized", jsonError.get().error);
            assertEquals("No message available", jsonError.get().message);
            assertEquals("/books/1680502395", jsonError.get().path);
        }
    }
def "after an HttpClientException the response body can be bound to a POJO"() {
        when:
        client.toBlocking().exchange(HttpRequest.GET("/books/1680502395"),
                Argument.of(Book), <b>(1)</b>
                Argument.of(CustomError)) <b>(2)</b>

        then:
        def e = thrown(HttpClientResponseException)
        e.response.status == HttpStatus.UNAUTHORIZED

        when:
        Optional<CustomError> jsonError = e.response.getBody(CustomError)

        then:
        jsonError.isPresent()
        jsonError.get().status == 401
        jsonError.get().error == 'Unauthorized'
        jsonError.get().message == 'No message available'
        jsonError.get().path == '/books/1680502395'
    }
"after an httpclient exception the response body can be bound to a POJO" {
            try {
                client.toBlocking().exchange(HttpRequest.GET<Any>("/books/1680502395"),
                        Argument.of(Book::class.java), <b>(1)</b>
                        Argument.of(CustomError::class.java)) <b>(2)</b>
            } catch (e: HttpClientResponseException) {
                e.response.status shouldBe HttpStatus.UNAUTHORIZED
            }
        }
1 Success Type
2 Error Type

7.2 Proxying Requests with ProxyHttpClient

A common requirement in Microservice environments is to proxy requests in a Gateway Microservice to other backend Microservices.

The regularHttpClient API is designed around simplifying exchange of messages and is not really designed for proxying requests. For this use case you should use the theProxyHttpClient which can be used from ato proxy requests to backend Microservices.

The following example demonstrates rewriting requests under the URI /proxy to the URI /real onto the same server (clearly in a real environment you would generally proxy onto another server):

Customizing the Netty pipeline for Logbook

import io.micronaut.core.async.publisher.Publishers;
import io.micronaut.core.util.StringUtils;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.MutableHttpResponse;
import io.micronaut.http.annotation.Filter;
import io.micronaut.http.client.ProxyHttpClient;
import io.micronaut.http.filter.*;
import io.micronaut.http.filter.ServerFilterChain;
import io.micronaut.runtime.server.EmbeddedServer;
import org.reactivestreams.Publisher;

@Filter("/proxy/**")
public class ProxyFilter extends OncePerRequestHttpServerFilter { <b>(1)</b>
    private final ProxyHttpClient client;
    private final EmbeddedServer embeddedServer;

    public ProxyFilter(ProxyHttpClient client, EmbeddedServer embeddedServer) { <b>(2)</b>
        this.client = client;
        this.embeddedServer = embeddedServer;
    }

    @Override
    protected Publisher<MutableHttpResponse<?>> doFilterOnce(HttpRequest<?> request, ServerFilterChain chain) {
        return Publishers.map(client.proxy( <b>(3)</b>
                request.mutate() <b>(4)</b>
                        .uri(b -> b <b>(5)</b>
                                .scheme("http")
                                .host(embeddedServer.getHost())
                                .port(embeddedServer.getPort())
                                .replacePath(StringUtils.prependUri(
                                        "/real",
                                        request.getPath().substring("/proxy".length())
                                ))
                        )
                        .header("X-My-Request-Header", "XXX") <b>(6)</b>
        ), response -> response.header("X-My-Response-Header", "YYY"));
    }
}

Customizing the Netty pipeline for Logbook

import io.micronaut.core.async.publisher.Publishers
import io.micronaut.core.util.StringUtils
import io.micronaut.http.HttpRequest
import io.micronaut.http.MutableHttpResponse
import io.micronaut.http.annotation.Filter
import io.micronaut.http.client.ProxyHttpClient
import io.micronaut.http.filter.*
import io.micronaut.http.filter.ServerFilterChain
import io.micronaut.http.uri.UriBuilder
import io.micronaut.runtime.server.EmbeddedServer
import org.reactivestreams.Publisher


@Filter("/proxy/**")
class ProxyFilter extends OncePerRequestHttpServerFilter { <b>(1)</b>
    private final ProxyHttpClient client
    private final EmbeddedServer embeddedServer

    ProxyFilter(ProxyHttpClient client, EmbeddedServer embeddedServer) { <b>(2)</b>
        this.client = client
        this.embeddedServer = embeddedServer
    }

    @Override
    protected Publisher<MutableHttpResponse<?>> doFilterOnce(HttpRequest<?> request, ServerFilterChain chain) {
        return Publishers.map(client.proxy( <b>(3)</b>
                request.mutate() <b>(4)</b>
                        .uri { UriBuilder b -> <b>(5)</b>
                            b.with {
                                scheme("http")
                                host(embeddedServer.getHost())
                                port(embeddedServer.getPort())
                                replacePath(StringUtils.prependUri(
                                                "/real",
                                                request.getPath().substring("/proxy".length())
                                ))
                            }
                        }
                        .header("X-My-Request-Header", "XXX") <b>(6)</b>
        ), { MutableHttpResponse<?> response -> response.header("X-My-Response-Header", "YYY")})
    }
}

Customizing the Netty pipeline for Logbook

import io.micronaut.core.async.publisher.Publishers
import io.micronaut.core.util.StringUtils
import io.micronaut.http.*
import io.micronaut.http.annotation.Filter
import io.micronaut.http.client.ProxyHttpClient
import io.micronaut.http.filter.*
import io.micronaut.http.uri.UriBuilder
import io.micronaut.runtime.server.EmbeddedServer
import org.reactivestreams.Publisher
import java.util.function.Function

@Filter("/proxy/**")
class ProxyFilter(
        private val client: ProxyHttpClient, <b>(2)</b>
        private val embeddedServer: EmbeddedServer) : OncePerRequestHttpServerFilter() { <b>(1)</b>
    override fun doFilterOnce(request: HttpRequest<*>, chain: ServerFilterChain): Publisher<MutableHttpResponse<*>> {
        return Publishers.map(client.proxy( <b>(3)</b>
                request.mutate() <b>(4)</b>
                        .uri { b: UriBuilder -> <b>(5)</b>
                            b.apply {
                                scheme("http")
                                host(embeddedServer.host)
                                port(embeddedServer.port)
                                replacePath(StringUtils.prependUri(
                                        "/real",
                                        request.path.substring("/proxy".length)
                                ))
                            }
                        }
                        .header("X-My-Request-Header", "XXX") <b>(6)</b>
        ), Function { response: MutableHttpResponse<*> -> response.header("X-My-Response-Header", "YYY") })
    }

}
1 The filter extends OncePerRequestHttpServerFilter
2 TheProxyHttpClient is injected into the constructor.
3 The proxy method is used to proxy the request
4 The request is mutated to modify the URI and include an additional header
5 Here theUriBuilder API is used to rewrite the URI
6 Additional request and response headers are included
TheProxyHttpClient API is a low level API that can be used to build a higher level abstraction such as an API Gateway.

7.3 Declarative HTTP Clients with @Client

Now that you have gathered an understanding of the workings of the lower level HTTP client, it is time to take a look at Micronaut’s support for declarative clients via theClient annotation.

Essentially, the @Client annotation can be declared on any interface or abstract class and through the use ofthe abstract methods will be implemented for you at compile time, greatly simplifying the creation of HTTP clients.

Let’s start with a simple example. Given the following class:

Pet.java

public class Pet {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

Pet.java

class Pet {
    String name
    int age
}

Pet.java

class Pet {
    var name: String? = null
    var age: Int = 0
}

You can define a common interface for saving new Pet instances:

PetOperations.java

import io.micronaut.http.annotation.Post;
import io.micronaut.validation.Validated;
import io.reactivex.Single;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;

@Validated
public interface PetOperations {
    @Post
    Single<Pet> save(@NotBlank String name, @Min(1L) int age);
}

PetOperations.java

import io.micronaut.http.annotation.Post
import io.micronaut.validation.Validated
import io.reactivex.Single

import javax.validation.constraints.Min
import javax.validation.constraints.NotBlank

@Validated
interface PetOperations {
    @Post
    Single<Pet> save(@NotBlank String name, @Min(1L) int age)
}

PetOperations.java

import io.micronaut.http.annotation.Post
import io.micronaut.validation.Validated
import io.reactivex.Single

import javax.validation.constraints.Min
import javax.validation.constraints.NotBlank


@Validated
interface PetOperations {
    @Post
    fun save(@NotBlank name: String, @Min(1L) age: Int): Single<Pet>
}

Note how the interface uses Micronaut’s HTTP annotations which are usable on both the server and client side. Also, as you can see you can use javax.validation constraints to validate arguments.

Be aware that some annotations, such asProduces andConsumes, have different semantics between server and client side usage. For example, @Produces on a controller method (server side) indicates how the method’s return value is formatted, while @Produces on a client indicates how the method’s parameters are formatted when sent to the server. While this may seem a little confusing at first, it is actually logical considering the different semantics between a server producing/consuming vs a client: A server consumes an argument and returns a response to the client, whereas a client consumes an argument and sends output to a server.

Additionally, to use the javax.validation features you should have the validation module on your classpath:

implementation("io.micronaut:micronaut-validator")
<dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-validator</artifactId>
</dependency>

On the server-side of Micronaut you can implement the PetOperations interface:

PetController.java

import io.micronaut.http.annotation.Controller;
import io.reactivex.Single;

@Controller("/pets")
public class PetController implements PetOperations {

    @Override
    public Single<Pet> save(String name, int age) {
        Pet pet = new Pet();
        pet.setName(name);
        pet.setAge(age);
        // save to database or something
        return Single.just(pet);
    }
}

PetController.java

import io.micronaut.http.annotation.Controller
import io.reactivex.Single

@Controller("/pets")
class PetController implements PetOperations {

    @Override
    Single<Pet> save(String name, int age) {
        Pet pet = new Pet()
        pet.setName(name)
        pet.setAge(age)
        // save to database or something
        return Single.just(pet)
    }
}

PetController.java

import io.micronaut.http.annotation.Controller
import io.reactivex.Single


@Controller("/pets")
open class PetController : PetOperations {

    override fun save(name: String, age: Int): Single<Pet> {
        val pet = Pet()
        pet.name = name
        pet.age = age
        // save to database or something
        return Single.just(pet)
    }
}

You can then define a declarative client in src/test/java that uses @Client to automatically, at compile time, implement a client:

PetClient.java

import io.micronaut.http.client.annotation.Client;
import io.reactivex.Single;

@Client("/pets") <b>(1)</b>
public interface PetClient extends PetOperations { <b>(2)</b>

    @Override
    Single<Pet> save(String name, int age); <b>(3)</b>
}

PetClient.java

import io.micronaut.http.client.annotation.Client
import io.reactivex.Single

@Client("/pets") <b>(1)</b>
interface PetClient extends PetOperations { <b>(2)</b>

    @Override
    Single<Pet> save(String name, int age) <b>(3)</b>
}

PetClient.java

import io.micronaut.http.client.annotation.Client
import io.reactivex.Single


@Client("/pets") <b>(1)</b>
interface PetClient : PetOperations { <b>(2)</b>

    override fun save(name: String, age: Int): Single<Pet>  <b>(3)</b>
}
1 TheClient annotation is used with a value relative to the current server. In this case /pets
2 The interface extends from PetOperations
3 The save method is overridden. See warning below.
Notice in the above example we override the save method. This is necessary if you compile without the -parameters option since Java does not retain parameters names in the byte code otherwise. If you compile with -parameters then overriding is not necessary.

Once you have defined a client you can simply @Inject it wherever you may need it.

Recall that the value of @Client can be:

  • An absolute URI. Example https://api.twitter.com/1.1

  • A relative URI, in which case the server targeted will be the current server (useful for testing)

  • A service identifier. See the section onfor more information on this topic.

In a production deployment you would typically use a service ID andto discover services automatically.

Another important thing to notice regarding the save method in the example above is that is returns a Single type.

This is a non-blocking reactive type and typically you want your HTTP clients not to block. There are cases where you may want to write an HTTP client that does block (such as in unit test cases), but this are rare.

The following table illustrates common return types usable with@Client:

Table 1. Micronaut Response Types
Type Description Example Signature

Publisher

Any type that implements the Publisher interface

Flowable<String> hello()

HttpResponse

AnHttpResponse and optional response body type

Single<HttpResponse<String>> hello()

Publisher

A Publisher implementation that emits a POJO

Mono<Book> hello()

CompletableFuture

A Java CompletableFuture instance

CompletableFuture<String> hello()

CharSequence

A blocking native type. Such as String

String hello()

T

Any simple POJO type.

Book show()

Generally, any reactive type that can be converted to the Publisher interface is supported as a return type including (but not limited to) the reactive types defined by RxJava 1.x, RxJava 2.x and Reactor 3.x.

Returning CompletableFuture instances is also supported. Note that returning any other type will result in a blocking request and is not recommended other than for testing.

7.3.1 Customizing Parameter Binding

The previous example presented a trivial example that uses the parameters of a method to represent the body of a POST request:

PetOperations.java

@Post
Single<Pet> save(@NotBlank String name, @Min(1L) int age);

The save method when called will perform an HTTP POST with the following JSON by default:

Example Produced JSON

{"name":"Dino", "age":10}

You may however want to customize what is sent as the body, the parameters, URI variables and so on. The@Client annotation is very flexible in this regard and supports the sameHTTP Annotations as Micronaut’s HTTP server.

For example, the following defines a URI template and the name parameter is used as part of the URI template, whilst@Body is used declare that the contents to send to the server are represented by the Pet POJO:

PetOperations.java

@Post("/{name}")
Single<Pet> save(
    @NotBlank String name, <i data-value="1"></i><b>(1)</b>
    @Body @Valid Pet pet) <i data-value="2"></i><b>(2)</b>
1 The name parameter, included as part of the URI, and declared @NotBlank
2 The pet parameter, used to encode the body and declared @Valid

The following table summarizes the parameter annotations, their purpose, and provides an example:

Table 1. Parameter Binding Annotations
Annotation Description Example

@Body

Allows to specify the parameter that is the body of the request

@Body String body

@CookieValue

Allows specifying parameters that should be sent as cookies

@CookieValue String myCookie

@Header

Allows specifying parameters that should be sent as HTTP headers

@Header String contentType

@QueryValue

Allows customizing the name of the URI parameter to bind from

@QueryValue("userAge") Integer age

@PathVariable

Used to bind a parameter exclusively from a.

@PathVariable Long id

@RequestAttribute

Allows specifying parameters that should be set as request attributes

@RequestAttribute Integer locationId

Type Based Binding Parameters

Some parameters are recognized by their type instead of their annotation. The following table summarizes the parameter types, their purpose, and provides an example:

Type Description Example

BasicAuth

Allows binding of basic authorization credentials

BasicAuth basicAuth

7.3.2 Streaming with @Client

The@Client annotation can also handle streaming HTTP responses.

Streaming JSON with @Client

For example to write a client that streams data from the controller defined in thesection of the documentation you can simply define a client that returns an unbound Publisher such as a RxJava Flowable or Reactor Flux :

HeadlineClient.java

import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.annotation.Client;
import io.reactivex.Flowable;

@Client("/streaming")
public interface HeadlineClient {

    @Get(value = "/headlines", processes = MediaType.APPLICATION_JSON_STREAM) <b>(1)</b>
    Flowable<Headline> streamHeadlines(); <b>(2)</b>

HeadlineClient.java

import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Get
import io.micronaut.http.client.annotation.Client
import io.reactivex.Flowable

@Client("/streaming")
interface HeadlineClient {

    @Get(value = "/headlines", processes = MediaType.APPLICATION_JSON_STREAM) <b>(1)</b>
    Flowable<Headline> streamHeadlines() <b>(2)</b>

}

HeadlineClient.java

import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Get
import io.micronaut.http.client.annotation.Client
import io.reactivex.Flowable


@Client("/streaming")
interface HeadlineClient {

    @Get(value = "/headlines", processes = [MediaType.APPLICATION_JSON_STREAM]) <b>(1)</b>
    fun streamHeadlines(): Flowable<Headline>  <b>(2)</b>
1 The @Get method is defined as processing responses of type APPLICATION_JSON_STREAM
2 A Flowable is used as the return type

The following example shows how the previously defined HeadlineClient can be invoked from a JUnit test:

Streaming HeadlineClient

@Test
    public void testClientAnnotationStreaming() throws Exception {
        try( EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer.class) ) {
            HeadlineClient headlineClient = embeddedServer
                                                .getApplicationContext()
                                                .getBean(HeadlineClient.class); <b>(1)</b>

            Maybe<Headline> firstHeadline = headlineClient.streamHeadlines().firstElement(); <b>(2)</b>

            Headline headline = firstHeadline.blockingGet(); <b>(3)</b>

            assertNotNull( headline );
            assertTrue( headline.getText().startsWith("Latest Headline") );
        }
    }

Streaming HeadlineClient

void "test client annotation streaming"() throws Exception {
        when:
        HeadlineClient headlineClient = embeddedServer.getApplicationContext()
                                            .getBean(HeadlineClient.class) <b>(1)</b>

        Maybe<Headline> firstHeadline = headlineClient.streamHeadlines().firstElement() <b>(2)</b>

        Headline headline = firstHeadline.blockingGet() <b>(3)</b>

        then:
        null != headline
        headline.getText().startsWith("Latest Headline")
    }

Streaming HeadlineClient

"test client annotation streaming" {
            val headlineClient = embeddedServer
                    .applicationContext
                    .getBean(HeadlineClient::class.java) <b>(1)</b>

            val firstHeadline = headlineClient.streamHeadlines().firstElement() <b>(2)</b>

            val headline = firstHeadline.blockingGet() <b>(3)</b>

            headline shouldNotBe null
            headline.text shouldStartWith "Latest Headline"
        }
1 The client is retrieved from theApplicationContext
2 The firstElement method is used to return the first emitted item from the Flowable as a Maybe .
3 The blockingGet() is used in the test to retrieve the result.

Streaming Clients and Response Types

The example defined in the previous section expects the server to respond with a stream of JSON objects and the content type to be application/x-json-stream . For example:

A JSON Stream

{"title":"The Stand"}
{"title":"The Shining"}

The reason for this is simple, a sequence of JSON object is not, in fact, valid JSON and hence the response content type cannot be application/json . For the JSON to be valid it would have to return an array:

A JSON Array

[
    {"title":"The Stand"},
    {"title":"The Shining"}
]

Micronaut’s client does however support streaming of both individual JSON objects via application/x-json-stream and also JSON arrays defined with application/json .

If the server returns application/json and a non-single Publisher is returned (such as an Flowable or a Reactor Flux ) then the client with stream the array elements as they become available.

Streaming Clients and Read Timeout

When streaming responses from servers, the underlying HTTP client will not apply the default readTimeout setting (which defaults to 10 seconds) of the HttpClientConfiguration since the delay between reads for streaming responses may differ from normal reads.

Instead the read-idle-timeout setting (which defaults to 60 seconds) is used to dictate when a connection should be closed after becoming idle.

If you are streaming data from a server that defines a longer delay than 60 seconds between items being sent to the client you should adjust the readIdleTimeout . The following configuration in application.yml demonstrates how:

Adjusting the readIdleTimeout

micronaut:
    http:
        client:
            read-idle-timeout: 5m

The above example sets the readIdleTimeout to 5 minutes.

Streaming Server Sent Events

Micronaut features a native client for Server Sent Events (SSE) defined by the interfaceSseClient.

You can use this client to stream SSE events from any server that emits them.

Although SSE streams are typically consumed by a browser EventSource , there are a few cases where you may wish to consume a SSE stream viaSseClient such as in unit testing or when a Micronaut service acts as a gateway for another service.

The@Client annotation also supports consuming SSE streams. For example, consider the following controller method that produces a stream of SSE events:

SSE Controller

@Get(value = "/headlines", processes = MediaType.TEXT_EVENT_STREAM) <b>(1)</b>
    Flowable<Event<Headline>> streamHeadlines() {
        return Flowable.<Event<Headline>>create((emitter) -> {  <b>(2)</b>
            Headline headline = new Headline();
            headline.setText("Latest Headline at " + ZonedDateTime.now());
            emitter.onNext(Event.of(headline));
            emitter.onComplete();
        }, BackpressureStrategy.BUFFER).repeat(100) <b>(3)</b>
          .delay(1, TimeUnit.SECONDS); <b>(4)</b>
    }

SSE Controller

@Get(value = "/headlines", processes = MediaType.TEXT_EVENT_STREAM) <b>(1)</b>
    Flowable<Event<Headline>> streamHeadlines() {
        Flowable.<Event<Headline>>create( { emitter -> <b>(2)</b>
            Headline headline = new Headline()
            headline.setText("Latest Headline at " + ZonedDateTime.now())
            emitter.onNext(Event.of(headline))
            emitter.onComplete()
        }, BackpressureStrategy.BUFFER).repeat(100) <b>(3)</b>
         .delay(1, TimeUnit.SECONDS) <b>(4)</b>
    }

SSE Controller

@Get(value = "/headlines", processes = [MediaType.TEXT_EVENT_STREAM]) <b>(1)</b>
    internal fun streamHeadlines(): Flowable<Event<Headline>> {
        return Flowable.create<Event<Headline>>( {  <b>(2)</b>
            emitter ->
            val headline = Headline()
            headline.text = "Latest Headline at " + ZonedDateTime.now()
            emitter.onNext(Event.of(headline))
            emitter.onComplete()
        }, BackpressureStrategy.BUFFER).repeat(100) <b>(3)</b>
                .delay(1, TimeUnit.SECONDS) <b>(4)</b>
    }
1 The controller defines a@Get annotation that produces a MediaType.TEXT_EVENT_STREAM
2 The method itself uses Reactor to emit a hypothetical Headline object
3 The repeat method is used to repeat the emission 100 times
4 With a delay of 1 second between each item emitted.

Notice that the return type of the controller is alsoEvent and that the Event.of method is used to create events to stream to the client.

To define a client that consumes the events you simply have to define a method that processes MediaType.TEXT_EVENT_STREAM :

SSE Client

@Client("/streaming/sse")
public interface HeadlineClient {

    @Get(value = "/headlines", processes = MediaType.TEXT_EVENT_STREAM)
    Flowable<Event<Headline>> streamHeadlines();
}

SSE Client

@Client("/streaming/sse")
interface HeadlineClient {

    @Get(value = "/headlines", processes = MediaType.TEXT_EVENT_STREAM)
    Flowable<Event<Headline>> streamHeadlines()
}

SSE Client

@Client("/streaming/sse")
interface HeadlineClient {

    @Get(value = "/headlines", processes = [MediaType.TEXT_EVENT_STREAM])
    fun streamHeadlines(): Flowable<Event<Headline>>
}

The generic type of the Flux or Flowable can be either anEvent, in which case you will receive the full event object, or a POJO, in which case you will receive only the data contained within the event converted from JSON.

7.3.3 Error Responses

If an HTTP response is returned with a code of 400 or above, an HttpClientResponseException is created. The exception contains the original response. How that exception gets thrown depends on the return type of the method.

  • For reactive response types, the exception is passed through the publisher as an error.

  • For blocking response types, the exception is thrown and should be caught and handled by the caller.

The one exception to this rule is HTTP Not Found (404) responses. This exception only applies to the declarative client.

HTTP Not Found (404) responses for blocking return types is not considered an error condition and the client exception will not be thrown. That behavior includes methods that return void .

If the method returns an HttpResponse , the original response will be returned. If the return type is Optional , an empty optional will be returned. For all other types, null will be returned.

7.3.4 Customizing Request Headers

Customizing the request headers deserves special mention as there are several ways that can be accomplished.

Populating Headers Using Configuration

The@Header annotation can be declared at the type level and is repeatable such that it is possible to drive the request headers sent via configuration using annotation metadata.

The following example serves to illustrate this:

Defining Headers via Configuration

@Client("/pets")
@Header(name="X-Pet-Client", value="${pet.client.id}")
public interface PetClient extends PetOperations {

    @Override
    Single<Pet> save(String name, int age);

    @Get("/{name}")
    Single<Pet> get(String name);
}

Defining Headers via Configuration

@Client("/pets")
@Header(name="X-Pet-Client", value='${pet.client.id}')
interface PetClient extends PetOperations {

    @Override
    Single<Pet> save(String name, int age)

    @Get("/{name}")
    Single<Pet> get(String name)
}

Defining Headers via Configuration

@Client("/pets")
@Header(name = "X-Pet-Client", value = "\${pet.client.id}")
interface PetClient : PetOperations {

    override fun save(name: String, age: Int): Single<Pet>

    @Get("/{name}")
    operator fun get(name: String): Single<Pet>
}

The above example defines a@Header annotation on the PetClient interface that reads a property using property placeholder configuration called pet.client.id .

In your application configuration you then set the following in application.yml to populate the value:

Configuring Headers in YAML

pet:
    client:
        id: foo

Alternatively you can supply a PET_CLIENT_ID environment variable and the value will be populated.

Populating Headers using an Client Filter

Alternatively if you need the ability to dynamically populate headers an alternative is to use a.

For more information on writing client filters see thesection of the guide.

7.3.5 Customizing Jackson Settings

As mentioned previously, Jackson is used for message encoding to JSON. A default Jackson ObjectMapper is configured and used by Micronaut HTTP clients.

You can override the settings used to construct the ObjectMapper using the properties defined by theJacksonConfiguration class in application.yml .

For example, the following configuration enabled indented output for Jackson:

Example Jackson Configuration

jackson:
    serialization:
        indentOutput: true

However, these settings apply globally and impact both how the HTTP server renders JSON and how JSON is sent from the HTTP client. Given that sometimes it useful to provide client specific Jackson settings which can be done with the@JacksonFeatures annotation on any client:

As an example, the following snippet is taken from Micronaut’s native Eureka client (which, of course, is built using Micronaut’s HTTP client):

Example of JacksonFeatures

@Client(id = EurekaClient.SERVICE_ID, path = "/eureka", configuration = EurekaConfiguration.class)
@JacksonFeatures(
    enabledSerializationFeatures = WRAP_ROOT_VALUE,
    disabledSerializationFeatures = WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED,
    enabledDeserializationFeatures = {UNWRAP_ROOT_VALUE, ACCEPT_SINGLE_VALUE_AS_ARRAY}
)
public interface EurekaClient {
    ...
}

The Eureka serialization format for JSON uses the WRAP_ROOT_VALUE serialization feature of Jackson, hence it is enabled just for that client.

If the customization offered by JacksonFeatures is not enough, you can also write a BeanCreatedEventListener for the ObjectMapper and add whatever customizations you need.

7.3.6 Retry and Circuit Breaker

Being able to recover from failure is critical for HTTP clients, and that is where the integratedincluded as part of Micronaut comes in really handy.

You can declare the@Retryable or@CircuitBreaker annotations on any@Client interface and the retry policy will be applied, for example:

Declaring @Retryable

@Client("/pets")
@Retryable
public interface PetClient extends PetOperations {

    @Override
    Single<Pet> save(String name, int age);
}

Declaring @Retryable

@Client("/pets")
@Retryable
interface PetClient extends PetOperations {

    @Override
    Single<Pet> save(String name, int age)
}

Declaring @Retryable

@Client("/pets")
@Retryable
interface PetClient : PetOperations {

    override fun save(name: String, age: Int): Single<Pet>
}

For more information on customizing retry, see the section on.

7.3.7 Client Fallbacks

In distributed systems, failure happens and it is best to be prepared for it and handle it in as graceful a manner possible.

In addition, when developing Microservices it is quite common to work on a single Microservice without other Microservices the project requires being available.

With that in mind Micronaut features a native fallback mechanism that is integrated intothat allows falling back to another implementation in the case of failure.

Using the@Fallback annotation you can declare a fallback implementation of a client that will be picked up and used once all possible retries have been exhausted.

In fact the mechanism is not strictly linked to Retry, you can declare any class as@Recoverable and if a method call fails (or, in the case of reactive types, an error is emitted) a class annotated with @Fallback will be searched for.

To illustrate this consider again the PetOperations interface declared earlier. You can define a PetFallback class that will be called in the case of failure:

Defining a Fallback

@Fallback
public class PetFallback implements PetOperations {
    @Override
    public Single<Pet> save(String name, int age) {
        Pet pet = new Pet();
        pet.setAge(age);
        pet.setName(name);
        return Single.just(pet);
    }
}

Defining a Fallback

@Fallback
class PetFallback implements PetOperations {
    @Override
    public Single<Pet> save(String name, int age) {
        Pet pet = new Pet()
        pet.setAge(age)
        pet.setName(name)
        return Single.just(pet)
    }
}

Defining a Fallback

@Fallback
open class PetFallback : PetOperations {
    override fun save(name: String, age: Int): Single<Pet> {
        val pet = Pet()
        pet.age = age
        pet.name = name
        return Single.just(pet)
    }
}
If you purely want to use fallbacks to help with testing against external Microservices you can define fallbacks in the src/test/java directory so they are not included in production code.

As you can see the fallback does not perform any network operations and is quite simple, hence will provide a successful result in the case of an external system being down.

Of course, the actual behaviour of the fallback is down to you. You could for example implement a fallback that pulls data from a local cache when the real data is not available, and sends alert emails to operations about downtime or whatever.

7.3.8 Netflix Hystrix Support

Using the CLI

If you are creating your project using the Micronaut CLI, supply the netflix-hystrix feature to configure Hystrix in your project:

$ mn create-app my-app --features netflix-hystrix

Netflix Hystrix is a fault tolerance library developed by the Netflix team and designed to improve resilience of inter process communication.

Micronaut features integration with Hystrix through the netflix-hystrix module, which you can add to your build.gradle or pom.xml :

implementation("io.micronaut.netflix:micronaut-netflix-hystrix")
<dependency>
    <groupId>io.micronaut.netflix</groupId>
    <artifactId>micronaut-netflix-hystrix</artifactId>
</dependency>

Using the @HystrixCommand Annotation

With the above dependency declared you can annotate any method (including methods defined on @Client interfaces) with the@HystrixCommand annotation and it will wrap the methods execution in a Hystrix command. For example:

Using @HystrixCommand

@HystrixCommand
String hello(String name) {
    return "Hello $name"
}
This works for reactive return types such as Flowable etc. as well and the reactive type will be wrapped in a HystrixObservableCommand .

The@HystrixCommand annotation also integrates with Micronauts support forand

For information on how to customize the Hystrix thread pool, group and properties see the javadoc for@HystrixCommand.

Enabling Hystrix Stream & Dashboard

You can enable a Server Sent Event stream to feed into the Hystrix Dashboard by setting the hystrix.stream.enabled setting to true in application.yml :

Enabling Hystrix Stream

hystrix:
    stream:
        enabled: true

This exposes a /hystrix.stream endpoint with the format the Hystrix Dashboard expects.

7.4 HTTP Client Filters

Often, you need to include the same HTTP headers or URL parameters in a set of requests against a third-party API or when calling another Microservice.

To simplify this, Micronaut includes the ability to defineHttpClientFilter classes that are applied to all matching HTTP client requests.

As an example say you want to build a client to communicate with the Bintray REST API . It would be terribly tedious to have to specify authentication for every single HTTP call.

To resolve this burden you can define a filter. The following is an example BintrayService :

class BintrayApi {
    public static final String URL = 'https://api.bintray.com'
}

@Singleton
class BintrayService {
    final RxHttpClient client;
    final String org;

    BintrayService(
            @Client(BintrayApi.URL) RxHttpClient client,           <b>(1)</b>
            @Value("${bintray.organization}") String org ) {
        this.client = client;
        this.org = org;
    }

    Flowable<HttpResponse<String>> fetchRepositories() {
        return client.exchange(HttpRequest.GET("/repos/" + org), String.class); <b>(2)</b>
    }

    Flowable<HttpResponse<String>> fetchPackages(String repo) {
        return client.exchange(HttpRequest.GET("/repos/" + org + "/" + repo + "/packages"), String.class); <b>(2)</b>
    }
}
class BintrayApi {
    public static final String URL = 'https://api.bintray.com'
}

@Singleton
class BintrayService {
    final RxHttpClient client
    final String org

    BintrayService(
            @Client(BintrayApi.URL) RxHttpClient client,           <b>(1)</b>
            @Value('${bintray.organization}') String org ) {
        this.client = client
        this.org = org
    }

    Flowable<HttpResponse<String>> fetchRepositories() {
        return client.exchange(HttpRequest.GET("/repos/$org"), String) <b>(2)</b>
    }

    Flowable<HttpResponse<String>> fetchPackages(String repo) {
        return client.exchange(HttpRequest.GET("/repos/${org}/${repo}/packages"), String) <b>(2)</b>
    }
}
class BintrayApi {
    public static final String URL = 'https://api.bintray.com'
}

@Singleton
internal class BintrayService(
        @param:Client(BintrayApi.URL) val client: RxHttpClient, <b>(1)</b>
        @param:Value("\${bintray.organization}") val org: String) {

    fun fetchRepositories(): Flowable<HttpResponse<String>> {
        return client.exchange(HttpRequest.GET<Any>("/repos/$org"), String::class.java) <b>(2)</b>
    }

    fun fetchPackages(repo: String): Flowable<HttpResponse<String>> {
        return client.exchange(HttpRequest.GET<Any>("/repos/$org/$repo/packages"), String::class.java) <b>(2)</b>
    }
}
1 AnRxHttpClient is injected for the Bintray API
2 The organization is configurable via configuration

The Bintray API is secured. To authenticate you need to add an Authorization header for every request. You could modify fetchRepositories and fetchPackages methods to include the necessary HTTP Header for each request. Using a filter is much simpler though:

@Filter("/repos/**") <b>(1)</b>
class BintrayFilter implements HttpClientFilter {

    final String username;
    final String token;

    BintrayFilter(
            @Value("${bintray.username}") String username, <b>(2)</b>
            @Value("${bintray.token}") String token ) { <b>(2)</b>
        this.username = username;
        this.token = token;
    }

    @Override
    public Publisher<? extends HttpResponse<?>> doFilter(MutableHttpRequest<?> request, ClientFilterChain chain) {
        return chain.proceed(
                request.basicAuth(username, token) <b>(3)</b>
        );
    }
}
@Filter('/repos/**') <b>(1)</b>
class BintrayFilter implements HttpClientFilter {


    final String username
    final String token

    BintrayFilter(
            @Value('${bintray.username}') String username, <b>(2)</b>
            @Value('${bintray.token}') String token ) { <b>(2)</b>
        this.username = username
        this.token = token
    }

    @Override
    Publisher<? extends HttpResponse<?>> doFilter(MutableHttpRequest<?> request, ClientFilterChain chain) {
        return chain.proceed(
                request.basicAuth(username, token) <b>(3)</b>
        )
    }
}
@Filter("/repos/**") <b>(1)</b>
internal class BintrayFilter(
        @param:Value("\${bintray.username}") val username: String, <b>(2)</b>
        @param:Value("\${bintray.token}") val token: String)<b>(2)</b>
    : HttpClientFilter {

    override fun doFilter(request: MutableHttpRequest<*>, chain: ClientFilterChain): Publisher<out HttpResponse<*>> {
        return chain.proceed(
                request.basicAuth(username, token) <b>(3)</b>
        )
    }
}
1 You can match only a subset of paths with a Client filter.
2 The username and token are injected via configuration
3 The basicAuth method is used include the HTTP BASIC credentials

Now, whenever you invoke the bintrayService.fetchRepositories() method, the Authorization HTTP header is included in the request.

Injecting Another Client into a HttpClientFilter

It should be noted that in order to create aRxHttpClient Micronaut needs to resolve all of the HttpClientFilter instances which creates a circular dependency in the case where you need to inject anotherRxHttpClient or a @Client bean into an instance of a HttpClientFilter .

To resolve this issue you should use the javax.inject.Provider interface to inject anotherRxHttpClient or a @Client bean into an instance of HttpClientFilter .

The following example which implements a filter that allows authentication between services on Google Cloud Run demonstrates how to use Provider to inject another client:

import io.micronaut.context.annotation.Requires;
import io.micronaut.context.env.Environment;
import io.micronaut.http.*;
import io.micronaut.http.annotation.Filter;
import io.micronaut.http.client.RxHttpClient;
import io.micronaut.http.filter.ClientFilterChain;
import io.micronaut.http.filter.HttpClientFilter;
import io.reactivex.Flowable;
import org.reactivestreams.Publisher;
import javax.inject.Provider;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;

@Requires(env = Environment.GOOGLE_COMPUTE)
@Filter(patterns = "/google-auth/api/**")
public class GoogleAuthFilter implements HttpClientFilter {
    private final Provider<RxHttpClient> authClientProvider;

    public GoogleAuthFilter(Provider<RxHttpClient> httpClientProvider) { <b>(1)</b>
        this.authClientProvider = httpClientProvider;
    }

    @Override
    public Publisher<? extends HttpResponse<?>> doFilter(MutableHttpRequest<?> request, ClientFilterChain chain) {
        Flowable<String> token = Flowable.fromCallable(() -> encodeURI(request))
                .flatMap(authURI -> authClientProvider.get().retrieve(HttpRequest.GET(authURI).header( <b>(2)</b>
                        "Metadata-Flavor", "Google"
                )));

        return token.flatMap(t -> chain.proceed(request.bearerAuth(t)));
    }

    private String encodeURI(MutableHttpRequest<?> request) throws UnsupportedEncodingException {
        URI fullURI = request.getUri();
        String receivingURI = fullURI.getScheme() + "://" + fullURI.getHost();
        return "http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=" +
                URLEncoder.encode(receivingURI, "UTF-8");
    }
}
import io.micronaut.context.annotation.Requires
import io.micronaut.context.env.Environment
import io.micronaut.http.HttpRequest
import io.micronaut.http.HttpResponse
import io.micronaut.http.MutableHttpRequest
import io.micronaut.http.annotation.Filter
import io.micronaut.http.client.RxHttpClient
import io.micronaut.http.filter.ClientFilterChain
import io.micronaut.http.filter.HttpClientFilter
import io.reactivex.Flowable
import org.reactivestreams.Publisher

import javax.inject.Provider

@Requires(env = Environment.GOOGLE_COMPUTE)
@Filter(patterns = "/google-auth/api/**")
class GoogleAuthFilter implements HttpClientFilter {
    private final Provider<RxHttpClient> authClientProvider

    GoogleAuthFilter(Provider<RxHttpClient> httpClientProvider) { <b>(1)</b>
        this.authClientProvider = httpClientProvider
    }

    @Override
    Publisher<? extends HttpResponse<?>> doFilter(MutableHttpRequest<?> request, ClientFilterChain chain) {
        Flowable<String> token = Flowable.fromCallable(() -> encodeURI(request))
                .flatMap(authURI -> authClientProvider.get().retrieve(HttpRequest.GET(authURI).header( <b>(2)</b>
                        "Metadata-Flavor", "Google"
                )))

        return token.flatMap(t -> chain.proceed(request.bearerAuth(t)))
    }

    private static String encodeURI(MutableHttpRequest<?> request) {
        String receivingURI = "$request.uri.scheme://$request.uri.host"
        return "http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=" +
                URLEncoder.encode(receivingURI, "UTF-8")
    }
}
import io.micronaut.context.annotation.Requires
import io.micronaut.context.env.Environment
import io.micronaut.http.HttpRequest
import io.micronaut.http.HttpResponse
import io.micronaut.http.MutableHttpRequest
import io.micronaut.http.annotation.Filter
import io.micronaut.http.client.RxHttpClient
import io.micronaut.http.filter.ClientFilterChain
import io.micronaut.http.filter.HttpClientFilter
import io.reactivex.Flowable
import org.reactivestreams.Publisher
import java.net.URLEncoder
import javax.inject.Provider

@Requires(env = [Environment.GOOGLE_COMPUTE])
@Filter(patterns = ["/google-auth/api/**"])
class GoogleAuthFilter (
        private val authClientProvider: Provider<RxHttpClient>) : HttpClientFilter { <b>(1)</b>
    override fun doFilter(request: MutableHttpRequest<*>, chain: ClientFilterChain): Publisher<out HttpResponse<*>?> {
        val token = Flowable.fromCallable { encodeURI(request) }
                .flatMap { authURI: String ->
                    authClientProvider.get().retrieve(HttpRequest.GET<Any>(authURI).header( <b>(2)</b>
                            "Metadata-Flavor", "Google"
                    ))
                }
        return token.flatMap { t -> chain.proceed(request.bearerAuth(t)) }
    }

    private fun encodeURI(request: MutableHttpRequest<*>): String {
        val receivingURI = "${request.uri.scheme}://${request.uri.host}"
        return "http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=" +
                URLEncoder.encode(receivingURI, "UTF-8")
    }

}
1 The javax.inject.Provider interface is used to inject another client, avoiding a circular reference
2 The get() method of the Provider interface is used to obtain the client instance.

Filter Matching By Annotation

For cases where a filter should be applied to a client, regardless of the URL, filters can be matched by the presence of an annotation applied to both the filter and the client. Given the following client:

import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.annotation.Client;

@BasicAuth <b>(1)</b>
@Client("/message")
public interface BasicAuthClient {

    @Get
    String getMessage();
}
import io.micronaut.http.annotation.Get
import io.micronaut.http.client.annotation.Client

@BasicAuth <b>(1)</b>
@Client("/message")
interface BasicAuthClient {

    @Get
    String getMessage()
}
import io.micronaut.http.annotation.Get
import io.micronaut.http.client.annotation.Client

@BasicAuth <b>(1)</b>
@Client("/message")
interface BasicAuthClient {

    @Get
    fun getMessage(): String
}
1 The @BasicAuth annotation is applied to the client

The following filter will filter the client requests:

import io.micronaut.http.HttpResponse;
import io.micronaut.http.MutableHttpRequest;
import io.micronaut.http.filter.ClientFilterChain;
import io.micronaut.http.filter.HttpClientFilter;
import org.reactivestreams.Publisher;

import javax.inject.Singleton;

@BasicAuth <b>(1)</b>
@Singleton <b>(2)</b>
public class BasicAuthClientFilter implements HttpClientFilter {

    @Override
    public Publisher<? extends HttpResponse<?>> doFilter(MutableHttpRequest<?> request, ClientFilterChain chain) {
        return chain.proceed(request.basicAuth("user", "pass"));
    }
}
import io.micronaut.http.HttpResponse
import io.micronaut.http.MutableHttpRequest
import io.micronaut.http.filter.ClientFilterChain
import io.micronaut.http.filter.HttpClientFilter
import org.reactivestreams.Publisher

import javax.inject.Singleton

@BasicAuth <b>(1)</b>
@Singleton <b>(2)</b>
class BasicAuthClientFilter implements HttpClientFilter {

    @Override
    Publisher<? extends HttpResponse<?>> doFilter(MutableHttpRequest<?> request, ClientFilterChain chain) {
        chain.proceed(request.basicAuth("user", "pass"))
    }
}
import io.micronaut.http.HttpResponse
import io.micronaut.http.MutableHttpRequest
import io.micronaut.http.filter.ClientFilterChain
import io.micronaut.http.filter.HttpClientFilter
import org.reactivestreams.Publisher

import javax.inject.Singleton

@BasicAuth <b>(1)</b>
@Singleton <b>(2)</b>
class BasicAuthClientFilter : HttpClientFilter {

    override fun doFilter(request: MutableHttpRequest<*>, chain: ClientFilterChain): Publisher<out HttpResponse<*>> {
        return chain.proceed(request.basicAuth("user", "pass"))
    }
}
1 The same annotation, @BasicAuth , is applied to the filter
2 Normally the @Filter annotation makes filters singletons by default. Because the @Filter annotation is not used, the desired scope must be applied

The @BasicAuth annotation is just an example and can be replaced with your own custom annotation.

import io.micronaut.http.annotation.FilterMatcher;

import java.lang.annotation.*;

@FilterMatcher <b>(1)</b>
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.PARAMETER})
public @interface BasicAuth {
}
import io.micronaut.http.annotation.FilterMatcher

import java.lang.annotation.*

@FilterMatcher <b>(1)</b>
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target([ElementType.TYPE, ElementType.PARAMETER])
@interface BasicAuth {
}
import io.micronaut.http.annotation.FilterMatcher

@FilterMatcher <b>(1)</b>
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS, AnnotationTarget.VALUE_PARAMETER)
annotation class BasicAuth
1 The only requirement for custom annotations is that the @HttpFilterStereotype annotation must be present

7.5 HTTP Client Sample

Read the HTTP Client Guide (Java, Groovy ,Kotlin), a step-by-step tutorial, to learn more.

8 Cloud Native Features

The majority of frameworks in use today on the JVM were designed before the rise of cloud deployments and microservice architectures. Applications built with these frameworks were intended to be deployed to traditional Java containers. As a result, cloud support in these frameworks typically comes as an add-on rather than as core design features.

Micronaut was designed from the ground up for building microservices for the cloud. As a result, many key features that typically require external libraries or services are available within your application itself. To override one of the industry’s current favorite buzzwords, Micronaut applications are "natively cloud-native".

The following are some of the cloud-specific features that are integrated directly into the Micronaut runtime:

  • Distributed Configuration

  • Service Discovery

  • Client-Side Load-Balancing

  • Distributed Tracing

  • Serverless Functions

Many of the features in Micronaut and heavily inspired by features from Spring and Grails. This is by design and helps developers who are already familiar with systems such as Spring Cloud.

The following sections cover these features and how to use them.

8.1 Cloud Configuration

Applications that are built for the Cloud often need adapt to running in a Cloud environment, read and share configuration in a distributed manner and externalize configuration to the environment where necessary.

Micronaut’sconcept is by default Cloud platform aware and will make a best effort to detect the underlying active environment.

You can then use theRequires annotation toconditionally load bean definitions.

The following table summarizes the constants provided by theEnvironment interface and provides an example:

Table 1. Micronaut Environment Detection
Constant Description Requires Example Environment name

ANDROID

The application is running as an Android application

@Requires(env = Environment.ANDROID)

android

TEST

The application is running within a JUnit or Spock test

@Requires(env = Environment.TEST)

test

CLOUD

The application is running in a Cloud environment (present for all other cloud platform types)

@Requires(env = Environment.CLOUD)

cloud

AMAZON_EC2

Running on Amazon EC2

@Requires(env = Environment.AMAZON_EC2)

ec2

GOOGLE_COMPUTE

Running on Google Compute

@Requires(env = Environment.GOOGLE_COMPUTE)

gcp

KUBERNETES

Running on Kubernetes

@Requires(env = Environment.KUBERNETES)

k8s

HEROKU

Running on Heroku

@Requires(env = Environment.HEROKU)

heroku

CLOUD_FOUNDRY

Running on Cloud Foundry

@Requires(env = Environment.CLOUD_FOUNDRY)

pcf

AZURE

Running on Microsoft Azure

@Requires(env = Environment.AZURE)

azure

IBM

Running on IBM Cloud

@Requires(env = Environment.IBM)

ibm

DIGITAL_OCEAN

Running on Digital Ocean

@Requires(env = Environment.DIGITAL_OCEAN)

digitalocean

ORACLE_CLOUD

Running on Oracle Cloud

@Requires(env = Environment.ORACLE_CLOUD)

oraclecloud

Note that it may be the case that you have multiple active environment names since you may run Kubernetes on AWS for example.

In addition, using the value of the constants defined in the table above you can create environment specific configuration files. For example if you create a src/main/resources/application-gcp.yml file then that configuration will only be loaded when running on Google Compute.

Any configuration property in theEnvironment can also be set via an environment variable. For example, setting the CONSUL_CLIENT_HOST environment variable will override the host property inConsulConfiguration.

Using Cloud Instance Metadata

When Micronaut detects it is running on any of the cloud platforms listed above, such as Google Compute or AWS EC2, upon startup Micronaut will populate the interface ComputeInstanceMetadata .

Depending on the environment you are running in the backing implementation will be either GoogleComputeInstanceMetadata , AmazonEC2InstanceMetadata , DigitalOceanInstanceMetadata or OracleCloudInstanceMetadata with metadata found from the cloud provider metadata services.

All of this data is merged together into the metadata property for the runningServiceInstance.

If you need to access the metadata for your application instance you can use the interface EmbeddedServerInstance , and call getMetadata() which will get a map of all of the metadata.

If you are connecting remotely via client, the instance metadata can be referenced once you have retrieved aServiceInstance from either theLoadBalancer orDiscoveryClient APIs.

The Netflix Ribbon client side load balancer can be configured to use the metadata to do zone aware client side load balancing. SeeClient Side Load Balancing

To obtain metadata for a service viause theLoadBalancerResolver interface to resolve aLoadBalancer and obtain a reference to a service by identifier:

Obtaining Metadata for a Service instance

LoadBalancer loadBalancer = loadBalancerResolver.resolve("some-service");
Flowable.fromPublisher(
    loadBalancer.select()
).subscribe((instance) ->
    ConvertibleValues<String> metaData = instance.getMetadata();
    ...
);

The EmbeddedServerInstance is available through event listeners that listen for theServiceReadyEvent. The@EventListener annotation makes it easy to listen for the event inside one of your existing beans.

To obtain metadata for the locally running server use anfor theServiceReadyEvent:

Obtaining Metadata for a Local Server

@EventListener
void onServiceStarted(ServiceReadyEvent event) {
    ServiceInstance serviceInstance = event.getSource()
    ConvertibleValues<String> metadata = serviceInstance.metadata
}

8.1.1 Distributed Configuration

As you can see, Micronaut features a robust system for externalizing and adapting configuration to the environment inspired by similar approaches found in Grails and Spring Boot.

However, what if you want two Microservices to share configuration? Micronaut comes with built in APIs for doing distributed configuration.

TheConfigurationClient interface has a single method called getPropertySources that can be implemented to read and resolve configuration from distributed sources.

The getPropertySources returns a Publisher that emits zero or manyPropertySource instances.

The default implementation is DefaultCompositeConfigurationClient which merges all registered ConfigurationClient beans into a single bean.

You can either implement your ownConfigurationClient implementation or you can use one of the ones already built into Micronaut. The following sections cover those.

8.1.2 HashiCorp Consul Support

Consul is a popular Service Discovery and Distributed Configuration server provided by HashiCorp. Micronaut features a nativeConsulClient that is built using Micronaut’s support forDeclarative HTTP Clients.

Starting Consul

The quickest way to start using Consul is via Docker:

  1. Starting Consul with Docker

docker run -p 8500:8500 consul

Alternatively you can install and run a local Consul instance .

Enabling Distributed Configuration with Consul

Using the CLI

If you are creating your project using the Micronaut CLI, supply the config-consul feature to enable Consul’s distributed configuration in your project:

$ mn create-app my-app --features config-consul

To enable distributed configuration, similar to Spring Boot and Grails, you need to create a src/main/resources/bootstrap.yml configuration file and configure Consul as well as enable the configuration client:

bootstrap.yml

micronaut:
    application:
        name: hello-world
    config-client:
        enabled: true
consul:
    client:
        defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"

Once you have enabled distributed configuration you need to store the configuration you wish to share in Consul’s Key/Value store.

There are a number of different ways to do that.

Storing Configuration as Key/Value Pairs

One way is to store each key and value directly in Consul. In this case by default Micronaut will look for configuration in the /config folder of Consul.

You can alter the path searched for by setting consul.client.config.path

Within the /config folder Micronaut will search values within the following folders in order of precedence:

Table 1. Configuration Resolution Precedence
Folder Description

/config/application

Configuration shared by all applications

/config/application,prod

Configuration shared by all applications for the prod Environment

/config/[APPLICATION_NAME]

Application specific configuration, example /config/hello-world

/config/[APPLICATION_NAME],prod

Application specific configuration for an activeEnvironment

The value of APPLICATION_NAME is whatever your have configured micronaut.application.name to be in bootstrap.yml .

To see this in action use the following curl command to store a property called foo.bar with a value of myvalue in the folder /config/application .

Using curl to Write a Value

curl -X PUT -d @- localhost:8500/v1/kv/config/application/foo.bar <<< myvalue

If you now define a @Value("${foo.bar}") or call environment.getProperty(..) the value myvalue will be resolved from Consul.

Storing Configuration in YAML, JSON etc.

Some Consul users prefer storing configuration in blobs of a certain format, such as YAML. Micronaut supports this mode and supports storing configuration in either YAML, JSON or Java properties format.

The ConfigDiscoveryConfiguration has a number of configuration options for configuring how distributed configuration is discovered.

You can set the consul.client.config.format option to configure the format with which properties are read.

For example, to configure JSON:

application.yml

consul:
    client:
        config:
            format: JSON

Now write your configuration in JSON format to Consul:

Using curl write JSON

curl -X PUT  localhost:8500/v1/kv/config/application \
-d @- << EOF
{ "foo": {  "bar": "myvalue" } }
EOF

Storing Configuration as File References

Another popular option is git2consul which mirrors the contents of a Git repository to Consul’s Key/Value store.

You can setup a Git repository that contains files like application.yml , hello-world-test.json etc. and the contents of these files are cloned to Consul.

In this case each key in consul represents a file with an extension. For example /config/application.yml and you must configure the FILE format:

application.yml

consul:
    client:
        config:
            format: FILE

8.1.3 HashiCorp Vault Support

Micronaut features support for integration with HashiCorp Vault as a distributed configuration source.

To enable support for Vault Configuration simply add the following configuration to your bootstrap.yml file:

Integrating with HashiCorp Vault

micronaut:
    application:
        name: hello-world
    config-client:
        enabled: true

vault:
    client:
        config:
            enabled: true
Table 1. Configuration Properties for VaultClientConfiguration
Property Type Description

vault.client.uri

java.lang.String

Set the Vault Server Uri. Default value ("http://locahost:8200").

vault.client.token

java.lang.String

Set the Vault authentication token.

vault.client.kv-version

VaultClientConfiguration$VaultKvVersion

Set the version of the Vault Secret engine. V1 or V2. Default value (V2).

vault.client.secret-engine-name

java.lang.String

Set the name of the Vault Secret engine name. Default value ("secret").

vault.client.fail-fast

boolean

If set to true an exception will be thrown if configuration is not found for the application or any of its environments. Default value (false).

vault.client.http-version

HttpVersion

vault.client.log-level

LogLevel

vault.client.event-loop-group

java.lang.String

vault.client.ssl-configuration

SslConfiguration

vault.client.exception-on-error-status

boolean

vault.client.logger-name

java.lang.String

vault.client.follow-redirects

boolean

vault.client.default-charset

java.nio.charset.Charset

vault.client.channel-options

java.util.Map

vault.client.shutdown-timeout

java.time.Duration

vault.client.read-timeout

java.time.Duration

vault.client.read-idle-timeout

java.time.Duration

vault.client.connect-timeout

java.time.Duration

vault.client.connect-ttl

java.time.Duration

vault.client.num-of-threads

java.lang.Integer

vault.client.thread-factory

java.lang.Class

vault.client.max-content-length

int

vault.client.proxy-type

java.net.Proxy$Type

vault.client.proxy-address

java.net.SocketAddress

vault.client.proxy-username

java.lang.String

vault.client.proxy-password

java.lang.String

vault.client.proxy-selector

java.net.ProxySelector

Micronaut will use the configured micronaut.application.name to lookup property sources for the application from Vault.

Table 2. Configuration Resolution Precedence
Folder Description

/application

Configuration shared by all applications

/[APPLICATION_NAME]

Application specific configuration

/application/[ENV_NAME]

Configuration shared by all applications for an active environment name

/[APPLICATION_NAME]/[ENV_NAME]

Application specific configuration for an active environment name

See the Documentation for HashiCorp Vault for more information on how to setup the server.

8.1.4 Spring Cloud Config Support

Since 1.1, Micronaut features a native Spring Cloud Configuration for those who have not switched to a dedicated more complete solution like Consul.

To enable support for Spring Cloud Configuration simply add the following configuration to your bootstrap.yml file:

Integrating with Spring Cloud Configuration

micronaut:
    application:
        name: hello-world
    config-client:
        enabled: true
spring:
    cloud:
        config:
            enabled: true
            uri: http://localhost:8888/
            retry-attempts: 4 # optional, number of times to retry
            retry-delay: 2s # optional, delay between retries

Micronaut will use the configured micronaut.application.name to lookup property sources for the application from Spring Cloud config server configured via spring.cloud.config.uri .

See the Documentation for Spring Cloud Config Server for more information on how to setup the server.

8.1.5 AWS Parameter Store Support

Micronaut supports configuration sharing via AWS System Manager Parameter Store. You will need the following dependencies configured:

implementation("io.micronaut.aws:micronaut-aws-parameter-store")
<dependency>
    <groupId>io.micronaut.aws</groupId>
    <artifactId>micronaut-aws-parameter-store</artifactId>
</dependency>

To enable distributed configuration a src/main/resources/bootstrap.yml configuration file must be created and configured to use Parameter Store:

bootstrap.yml

micronaut:
    application:
        name: hello-world
    config-client:
        enabled: true

Unresolved directive in <stdin> - include::/home/travis/build/micronaut-projects/micronaut-core/build/generated/configurationProperties/io.micronaut.discovery.aws.parameterstore.AWSParameterStoreConfiguration.adoc[]

You can configure shared properties by going into the AWS Console → System Manager → Parameter Store

Micronaut will use a hierarchy to read the configuration values, and supports String , StringList , and SecureString types.

You can make environment specific configurations as well by including the environment name after an underscore _ . For example if your micronaut.application.name setting is set to helloworld then providing configuration values under helloworld_test will be applied only to the test environment.

Table 1. Configuration Resolution Precedence
Folder Description

/config/application

Configuration shared by all applications

/config/[APPLICATION_NAME]

Application specific configuration, example /config/hello-world

/config/application_prod

Configuration shared by all applications for the prod Environment

/config/[APPLICATION_NAME]_prod

Application specific configuration for an activeEnvironment

For example, if the configuration name /config/application_test/server.url is configured in AWS Parameter Store, then any application connecting to that parameter store can retrieve the value using server.url . If the application has micronaut.application.name configured to be myapp , then a value with the name /config/myapp_test/server.url will override the value just for that application.

Each level of the tree can be composed of key=value pairs. If you want multiple key value pairs, set the type to 'StringList'.

For special secure information, like keys or passwords, use the type "SecureString". KMS will be automatically invoked when you add and retrieve values and decrypt them with the default key store for your account. If you set the configuration to not use secure strings, they will be returned to you encrypted and you must manually decrypt them.

The https://github.com/micronaut-projects/micronaut-examples/aws repository showcases a sample application using this feature.

8.1.6 Oracle Cloud Vault Support

Micronaut supports loading configuration values stored in Oracle Cloud Vaults. To use this feature, add the following dependencies:

Example build.gradle for Oracle Cloud Vault
compile "io.micronaut:micronaut-discovery-client"
compile group: 'com.oracle.oci.sdk', name: 'oci-java-sdk-vault', version: '1.15.4'
compile group: 'com.oracle.oci.sdk', name: 'oci-java-sdk-secrets', version: '1.15.4'
compile group: 'com.oracle.oci.sdk', name: 'oci-java-sdk-common', version: '1.15.4'

To enable distributed configuration a src/main/resources/bootstrap.yml configuration file must be created and configured to use one or more Oracle Cloud Vaults:

bootstrap.yml

micronaut:
  application:
    name: vault-test
  config-client:
    enabled: true
oraclecloud:
  vault:
    config:
      enabled: true
    vaults:
      - ocid: ocid1.vault.oc1.phx...
        compartment-ocid: ocid1.compartment.oc1...
    use-instance-principal: false
    path-to-config: ~/.oci/config
    profile: DEFAULT
    region: US-PHOENIX-1
Table 1. Configuration Properties for OracleCloudVaultClientConfiguration
Property Type Description

oraclecloud.vault.vaults

java.util.List

A list of OracleCloudVaultClientConfiguration$OracleCloudVault objects that contain secrets that will be retrieved, decoded and set into your application as config variables.

oraclecloud.vault.use-instance-principal

boolean

Whether or not the configuration client should use Instance Principal Authentication to interact with the SDK. If this value is false, you must include a path to an OCI Config file to use Config File based auth.

oraclecloud.vault.path-to-config

java.lang.String

If you are not using Instance Principal auth then you must pass the path to a valid OCI config file. Default value "~/.oci/config".

oraclecloud.vault.profile

java.lang.String

Which profile in the config file should be used? Default value "DEFAULT".

oraclecloud.vault.region

java.lang.String

The OCI region Ex: 'US-PHOENIX-1'.

You can learn more about Oracle Cloud Vault Secrets by reading this blog post .

Each configured vault will be read and all of the secrets within the vault will be retrieved and set into configuration variables with the exact same name as the secret in the Oracle Cloud Vault.

For example, if you create a secret with the name of SECRET_ONE in your Oracle Cloud Vault, then it will be available to use in your application like any standard configuration variable:

@Value("${SECRET_ONE}") String secretOne

You can also use @PropertyName :

@Property(name = "SECRET_ONE") String secretOne

Another option is to inject your variables in to your configuration files which gives you the ability to store things like database passwords and API keys in your vault:

application.yaml

datasources:
  default:
    password: ${DB_PASSWORD}

Vault retrieved values are always String , but you can use @ConfigurationProperties on a bean in conjunction with your application.yml file to provide properly typed configuration variables.

So if you where to create secrets in your Oracle Cloud Vault like so:

Name Value

SECRET_ONE

Value One

SECRET_TWO

value two

SECRET_THREE

true

SECRET_FOUR

42

SECRET_FIVE

3.16

And then added the following to your application.yml file:

application.yml

secrets:
  one: ${SECRET_ONE}
  two: ${SECRET_TWO}
  three: ${SECRET_THREE}
  four: ${SECRET_FOUR}
  five: ${SECRET_FIVE}

You could add a config bean like so:

Config.java

@ConfigurationProperties("secrets")
public class Config {
    private String one;
    private String two;
    private boolean three;
    private int four;
    private Double five;

    /* getters/setters removed for brevity */
}

You could then inject and use this bean in your application with properly typed values.

HelloController.java

@Controller("/hello")
public class HelloController {

    private Config config;

    public HelloController(
            Config config
    ) {
        this.config = config;
    }

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

    @Get("/secret")
    public HttpResponse getSecret() {
        return HttpResponse.ok(config);
    }
}

Calling the /hello/secret endpoint would return:

{
  "one": "Value One",
  "two": "value two",
  "three": true,
  "four": 42,
  "five": 3.16
}
If you need support for different configurations per environment, you can create an environment specific bootstrap.yml file and utilize a different vault(s) configuration per environment.

8.2 Service Discovery

Using the CLI

If you are creating your project using the Micronaut CLI, supply either of discovery-consul or discovery-eureka features to enable service-discovery in your project:

$ mn create-app my-app --features discovery-consul

Service Discovery enables the ability for Microservices to find each other without necessarily knowing the physical location or IP address of associated services.

There are many ways Service Discovery can be implemented, including:

  • Manually implement Service Discovery using DNS without requiring a third party tool or component.

  • Use a discovery server such as Eureka , Consul or ZooKeeper .

  • Delegate the work to a container runtime, such as Kubernetes .

With that in mind, Micronaut tries to flexible to support all of these approaches. As of this writing, Micronaut features integrated support for the popular Service Discovery servers:

  • Eureka

  • Consul

To include Service Discovery in your application simply the first step is to add the discovery-client dependency to your application:

implementation("io.micronaut:micronaut-discovery-client")
<dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-discovery-client</artifactId>
</dependency>

The discovery-client dependency provides implementations of theDiscoveryClient interface.

TheDiscoveryClient is fairly simple and provides two main entry points:

Both methods return Publisher instances since the operation to retrieve service ID information may result in a blocking network call depending on the underlying implementation.

If you are using Micronaut’s cache module, the default implementation of the DiscoveryClient interface is CachingCompositeDiscoveryClient which merges all other DiscoveryClient beans into a single bean and provides caching of the results of the methods. The default behaviour is to cache for 30 seconds. This cache can be disabled in application configuration:

Disabling the Discovery Client Cache

micronaut:
    caches:
        discovery-client:
            enabled: false

Alternatively you can alter the cache’s expiration policy:

Configuring the Discovery Client Cache

micronaut:
    caches:
        discovery-client:
            expire-after-access: 60s

See the DiscoveryClientCacheConfiguration class for available configuration options.

8.2.1 Consul Support

Consul is a popular Service Discovery and Distributed Configuration server provided by HashiCorp. Micronaut features a native non-blockingConsulClient that is built using Micronaut’s support forDeclarative HTTP Clients.

Starting Consul

The quickest way to start using Consul is via Docker:

  1. Starting Consul with Docker. Currently only v1.2.x is supported.

docker run -p 8500:8500 consul:1.2.4

Alternatively you can install and run a local Consul instance .

Auto Registering with Consul

To register a Micronaut application with Consul simply add the necessaryConsulConfiguration. A minimal example can be seen below:

Auto Registering with Consul (application.yml)

micronaut:
    application:
        name: hello-world
consul:
  client:
    registration:
      enabled: true
    defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"
Using the Micronaut CLI you can quickly create a new service setup with Consul using: mn create-app my-app --features discovery-consul

The consul.client.defaultZone settings accepts a list of Consul servers to be used by default.

You could also simply set consul.client.host and consul.client.port , howeverConsulConfiguration allows you specify per zone discovery services for the purpose load balancing. A zone maps onto a AWS availability zone or a Google Cloud zone.

By default registering with Consul is disabled hence you should set consul.client.registration.enabled to true . Note that you may wish to do this only in your production configuration.

Running multiple instances of a service may require an additional configuration param. See below.

If you are running the same applications on the same port across different servers it is important to set the micronaut.application.instance.id property or you will experience instance registration collision.

micronaut:
  application:
    name: hello-world
    instance:
      id: ${random.shortuuid}

Customizing Consul Service Registration

TheConsulConfiguration class features a range of customization options for altering how an instance registers with Consul. You can customize the tags, the retry attempts, the fail fast behaviour and so on.

Notice too that ConsulConfiguration extends DiscoveryClientConfiguration which in turn extends HttpClientConfiguration allowing you to customize the settings for the Consul client, including read timeout, proxy configuration and so on.

For example:

Customizing Consul Registration Configuration

micronaut:
    application:
        name: hello-world
consul:
  client:
    registration:
      enabled: true
      # Alters the tags
      tags:
        - hello
        - world
      # Alters the metadata
      meta:
        some: value
        instance_type: t2.medium
      # Alters the retry count
      retry-count: 5
      # Alters fail fast behaviour
      fail-fast: false
    defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"

Discovery Services from Consul

To discovery other services you could manually interact with theDiscoveryClient, however typically instead you use theto declare how an HTTP client maps to a service.

For example the configuration in the previous section declared a value for micronaut.application.name of hello-world . This is the value that will be used as the service ID when registering with Consul.

Other services can discovery instances of the hello-world service simply by declaring a client as follows:

Using @Client to Discover Services

@Client(id = "hello-world")
interface HelloClient{
	...
}

Alternatively you can also use@Client as a qualifier to @Inject an instance ofHttpClient:

Using @Client to Discover Services

@Client(id = "hello-world")
@Inject
RxHttpClient httpClient;

Consul Health Checks

By default when registering with Consul Micronaut will register a TTL check . A TTL check basically means that if the application does not send a heartbeat back to Consul after a period of time the service is put in a failing state.

Micronaut applications feature a HeartbeatConfiguration which starts a thread usingHeartbeatTask that firesHeartbeatEvent instances.

The ConsulAutoRegistration class listens for these events and sends a callback to the /agent/check/pass/:check_id endpoint provided by Consul, effectively keeping the service alive.

With this arrangement the responsibility is on the Micronaut application to send TTL callbacks to Consul on a regular basis.

If you prefer you can push the responsibility for health checks to Consul itself by registering an HTTP check:

Consul HTTP Check Configuration

consul:
  client:
    registration:
       check:
          http: true

With this configuration option in place Consul will assume responsibility of invoking the Micronaut applications.

Controlling IP/Host Registration

Occasionally, depending on the deployment environment you may wish to expose the IP address and not the host name, since by default Micronaut will register with Consul with either the value of the HOST environment variable or the value configured via micronaut.server.host .

You can use the consul.client.registration.prefer-ip-address setting to indicate you would prefer to register with the IP address.

Micronaut will by default perform an IP lookup to try and figure out the IP address, however you can use the consul.client.registration.ip-addr setting to specify the IP address of the service directly.

Consul HTTP Check Configuration

consul:
  client:
    registration:
        ip-addr: <your base container ip>
        prefer-ip-address: true

This will tell Consul to register the IP that other instances can use to access your service and not the NAT IP it is running under (or 127.0.0.1).

If you use HTTP health checks (see the previous section) then Consul will use the configured IP address to check the Micronaut /health endpoint.

Consul HTTP Check Configuration

consul:
  client:
    registration:
        ip-addr: <your base container ip>
        prefer-ip-address: true
        check:
            http: true

8.2.2 Eureka Support

Netflix Eureka is a popular discovery server deployed at scale at organizations like Netflix.

Micronaut features a native non-blockingEurekaClient as part of the discovery-client module that does not require any additional third-party dependencies and is built using Micronaut’s support forDeclarative HTTP Clients.

Starting Eureka

The quickest way to start a Eureka server is to use to use Spring Boot’s Eureka starters .

As of this writing the official Docker images for Eureka are significantly out-of-date so it is recommended to create a Eureka server following the steps above.

Auto Registering with Eureka

The process to register a Micronaut application with Eureka is very similar to with Consul, as seen in the previous section, simply add the necessaryEurekaConfiguration. A minimal example can be seen below:

Auto Registering with Eureka (application.yml)

micronaut:
    application:
        name: hello-world
eureka:
  client:
    registration:
      enabled: true
    defaultZone: "${EUREKA_HOST:localhost}:${EUREKA_PORT:8761}"

Customizing Eureka Service Registration

You can customize various aspects of registration with Eureka using theEurekaConfiguration. Notice that EurekaConfiguration extends DiscoveryClientConfiguration which in turn extends HttpClientConfiguration allowing you to customize the settings for the Eureka client, including read timeout, proxy configuration and so on.

Example Eureka Configuration

eureka:
  client:
     readTimeout: 5s
     registration:
         asgName: myAsg # the auto scaling group name
         countryId: 10 # the country id
         vipAddress: 'myapp' # The Eureka VIP address
         leaseInfo:
            durationInSecs: 60 # The lease information
         metadata: # arbitrary instance metadata
            foo: bar
         retry-count: 10 # How many times to retry
         retry-delay: 5s # How long to wait between retries

         appname: some-app-name     # optional, eureka instance application name, defaults to ${micronaut.application.name}
         hostname: foo.example.com  # optional, exposed eureka instance hostname, useful in docker bridged network environments
         ip-addr: 1.2.3.4           # optional, exposed eureka instance ip address, useful in docker bridged network environments
         port: 9090                 # optional, exposed eureka instance port, useful in docker bridged network environments
Table 1. Configuration Properties for EurekaConfiguration$EurekaRegistrationConfiguration
Property Type Description

eureka.client.registration.secure-port

int

eureka.client.registration.port

int

eureka.client.registration.instance-id

java.lang.String

eureka.client.registration.asg-name

java.lang.String

eureka.client.registration.home-page-url

java.lang.String

eureka.client.registration.lease-info

LeaseInfo

eureka.client.registration.country-id

int

eureka.client.registration.status-page-url

java.lang.String

eureka.client.registration.health-check-url

java.lang.String

eureka.client.registration.secure-health-check-url

java.lang.String

eureka.client.registration.data-center-info

DataCenterInfo

eureka.client.registration.status

InstanceInfo$Status

eureka.client.registration.app-group-name

java.lang.String

eureka.client.registration.ip-addr

java.lang.String

eureka.client.registration.vip-address

java.lang.String

eureka.client.registration.secure-vip-address

java.lang.String

eureka.client.registration.metadata

java.util.Map

eureka.client.registration.lease-info.registration-timestamp

long

eureka.client.registration.lease-info.renewal-timestamp

long

eureka.client.registration.lease-info.eviction-timestamp

long

eureka.client.registration.lease-info.service-up-timestamp

long

eureka.client.registration.lease-info.duration-in-secs

int

eureka.client.registration.lease-info.renewal-interval-in-secs

int

eureka.client.registration.prefer-ip-address

boolean

eureka.client.registration.timeout

java.time.Duration

eureka.client.registration.fail-fast

boolean

eureka.client.registration.deregister

boolean

eureka.client.registration.enabled

boolean

eureka.client.registration.retry-count

int

eureka.client.registration.retry-delay

java.time.Duration

eureka.client.registration.health-path

java.lang.String

Eureka Basic Authentication

You can customize the Eureka credentials in the URI you specify to in defaultZone .

For example:

Auto Registering with Eureka

eureka:
  client:
    defaultZone: "https://${EUREKA_USERNAME}:${EUREKA_PASSWORD}@localhost:8761"

The above example externalizes configuration of the username and password Eureka to environment variables called EUREKA_USERNAME and EUREKA_PASSWORD .

Eureka Health Checks

Like Consul, the EurekaAutoRegistration will sendHeartbeatEvent instances with theHealthStatus of the Micronaut application to Eureka.

TheHealthMonitorTask will by default continuously monitor theHealthStatus of the application by running health checks and theCurrentHealthStatus will be sent to Eureka.

Secure Communication with Eureka

If you wish to configure HTTPS and have clients discovery Eureka instances and communicate over HTTPS then you should set the eureka.client.discovery.use-secure-port option to true to ensure that service communication happens over HTTPS and also configureappropriately for each instance.

8.2.3 Kubernetes Support

Kubernetes is a container runtime which has a whole bunch of features including integrated Service Discovery and Distributed Configuration.

Micronaut have a first-class integration with Kubernetes. Check the Micronaut Kubernetes documentation for more details.

8.2.4 AWS Route 53 Support

To use the Route 53 Service Discovery , you must meet the following criteria:

  • Run EC2 instances of some type

  • Have a domain name hosted in Route 53

  • Have a newer version of AWS-CLI (such as 14+)

Assuming you have those things, you are ready. It is not as fancy as Consul or Eureka, but other than some initial setup with the AWS-CLI, there is no other software running to go wrong. You can even support health checks if you add a custom health check to your service. If you would like to test if your account can create and use Service Discovery see the Integration Test section. More information can be found at https://docs.aws.amazon.com/Route53/latest/APIReference/overview-service-discovery.html .

Here are the steps:

  1. Use AWS-CLI to create a namespace. You can make either a public or private one depending on what IPs or subnets you are using

  2. Create a service with DNS Records with AWS-CLI command

  3. Add health checks or custom health checks (optional)

  4. Add Service ID to your application configuration file like so:

Sample application.yml

aws:
    route53:
        registration
            enabled: true
            aws-service-id: srv-978fs98fsdf
            namespace: micronaut.io
micronaut:
    application:
        name: something
  1. Make sure you have the following dependencies included in your build file:

implementation("io.micronaut.aws:micronaut-aws-route53")
<dependency>
    <groupId>io.micronaut.aws</groupId>
    <artifactId>micronaut-aws-route53</artifactId>
</dependency>
  1. On the client side, you will need the same dependencies and less configuration options:

Sample application.yml

aws:
    route53:
        discovery:
            client:
                enabled: true
                aws-service-id: srv-978fs98fsdf
                namespace-id: micronaut.io

You can then use theDiscoveryClient API to find other services registered via Route 53. For example:

Sample code for client

DiscoveryClient discoveryClient = embeddedServer.applicationContext.getBean(DiscoveryClient);
List<String> serviceIds = Flowable.fromPublisher(discoveryClient.getServiceIds()).blockingFirst();
List<ServiceInstance> instances = Flowable.fromPublisher(discoveryClient.getInstances(serviceIds.get(0))).blockingFirst();

Creating the Namespace

Namespaces are similar to a regular Route53 hosted zone, and they appear in the Route53 console but the console doesn’t support modifying them. You must use the AWS-CLI at this time for any Service Discovery functionality.

First decide if you are creating a public facing namespace or a private one, as the commands are different:

Creating Namespace

$ aws servicediscovery create-public-dns-namespace --name micronaut.io --create-request-id create-1522767790 --description adescrptionhere

or

$ aws servicediscovery create-private-dns-namespace --name micronaut.internal.io --create-request-id create-1522767790 --description adescrptionhere --vpc yourvpcID

When you run this you will get an operation ID. You can check the status with the get-operation CLI command:

Get Operation Results

$ aws servicediscovery get-operation --operation-id asdffasdfsda

You can use this command to get the status of any call you make that returns an operation id.

The result of the command will tell you the ID of the namespace. Write that down, you’ll need it for the next steps. If you get an error it will say what the error was.

Creating the Service & DNS Records

The next step is creating the Service and DNS records.

Create Service

$ aws create-service --name yourservicename --create-request-id somenumber --description someservicedescrption --dns-config NamespaceId=yournamespaceid,RoutingPolicy=WEIGHTED,DnsRecords=[{Type=A,TTL=1000},{Type=A,TTL=1000}]

The DnsRecord type can be A (ipv4), AAAA (ipv6), SRV , or CNAME . RoutingPolicy can be WEIGHTED or MULTIVALUE . Keep in mind CNAME must use weighted routing type, SRV must have a valid port configured.

If you want to add a health check, you can use the following syntax on the CLI:

Specifying a Health Check

Type=string,ResourcePath=string,FailureThreshold=integer

Type can be 'HTTP','HTTPS', or 'TCP'. You can only use a standard health check on a public namespace. See Custom Health Checks for private namespaces. Resource path should be a url that returns 200 OK if it’s healthy.

For a custom health check, you only need to specify --health-check-custom-config FailureThreshold=integer which will work on private namespaces as well.

This is also good because Micronaut will send out pulsation commands to let AWS know the instance is still healthy.

For more help run 'aws discoveryservice create-service help'.

You will get a service ID and an ARN back from this command if successful. Write that down, it’s going to go into the Micronaut configuration.

Setting up the configuration in Micronaut

Auto Naming Registration

You will need to add the configuration to make your applications register with Route 53 Auto-discovery:

Registration Properties

aws:
    route53:
        registration:
            enabled: true
            aws-service-id=<enter the service id you got after creation on aws cli>
        discovery:
            namespace-id=<enter the namespace id you got after creating the namespace>

Discovery Client Configuration

Discovery Properties

aws:
    route53:
        discovery:
            client
                enabled: true
                aws-service-id: <enter the service id you got after creation on aws cli>

You can also call the following methods by getting the bean "Route53AutoNamingClient":

Discovery Methods

// if serviceId is null it will use property "aws.route53.discovery.client.awsServiceId"
Publisher<List<ServiceInstance>> getInstances(String serviceId)
// reads property "aws.route53.discovery.namespaceId"
Publisher<List<String>> getServiceIds()

Integration Tests

If you set the environment variable AWS_SUBNET_ID and have credentials configured in your home directory that are valid (in ~/.aws/credentials ) you can run the integration tests. You will still need a domain hosted on route53 as well. This test will create a t2.nano instance, a namespace, service, and register that instance to service discovery. When the test completes it will remove/terminate all resources it spun up.

8.2.5 Manual Service Discovery Configuration

If you do not wish to involve a service discovery server like Consul or you are interacting with a third-party service that cannot register with Consul you can instead manually configure services that are available via Service discovery.

To do this you should use the micronaut.http.services setting. The following is an example configuration:

Manually configuring services

micronaut:
    http:
        services:
            foo:
                urls:
                    - http://foo1
                    - http://foo2

You can then inject a client with @Client("foo") and it will use the above configuration to load balance between the two configured servers.

You can override this configuration in production by specifying an environment variable such as MICRONAUT_HTTP_SERVICES_FOO_URLS=http://prod1,http://prod2

Note that by default no health checking will happen to assert that the referenced services are operational. You can alter that by enabling health checking and optionally specifying a health check path (the default is /health ):

Enabling Health Checking

micronaut:
    http:
        services:
            foo:
                ...
                health-check: true <i data-value="1"></i><b>(1)</b>
                health-check-interval: 15s <i data-value="2"></i><b>(2)</b>
                health-check-uri: /health <i data-value="3"></i><b>(3)</b>
1 Whether to health check the service
2 The interval to wait between checks
3 The URI to send the health check request to

Micronaut will start a background thread to check the health status of the service and if any of the configured services respond with an error code, they will be removed from the list of available services.

8.3 Client Side Load Balancing

Whenfrom Consul, Eureka or other Service Discovery servers theDiscoveryClient will emit a list of availableServiceInstance.

Micronaut by default will automatically perform Round Robin client-side load balancing using the servers in this list. This combined withadds extra resiliency to your Microservice infrastructure.

The load balancing itself is handled by theLoadBalancer interface which defines a LoadBalancer.select() method that returns a Publisher that emits aServiceInstance.

The Publisher is returned because the process for selecting aServiceInstance may result in a network operation depending on thestrategy employed.

The default implementation of theLoadBalancer interface is DiscoveryClientRoundRobinLoadBalancer . You can replace this strategy for another implementation if you wish to customize how client side load balancing is handled in Micronaut since there are many different ways you may wish to optimize load balancing.

For example, you may wish to load balance between services in a particular zone or you may wish to load balance between servers that have the best overall response time.

To replace theLoadBalancer used you should define a bean thatthe DiscoveryClientLoadBalancerFactory .

In fact that is exactly what the Netflix Ribbon support does, described in the next section.

8.3.1 Netflix Ribbon Support

Using the CLI

If you are creating your project using the Micronaut CLI, supply the netflix-ribbon feature to configure Netflix Ribbon in your project:

$ mn create-app my-app --features netflix-ribbon

Netflix Ribbon is a inter-process communication library used at Netflix that has support for customizable load balancing strategies.

If you need more flexibility in how your application performs client-side load balancing then you may wish use Micronaut’s Netflix Ribbon support.

To add Ribbon support to your application add the netflix-ribbon configuration to build.gradle or pom.xml :

build.gradle

compile "io.micronaut.netflix:micronaut-netflix-ribbon"

TheLoadBalancer implementations will now beRibbonLoadBalancer instances.

Ribbon’s Configuration options can be set using the ribbon namespace in configuration. For example in application.yml :

Configuring Ribbon

ribbon:
    VipAddress: test
    ServerListRefreshInterval: 2000

Each discovered client can also be configured under ribbon.clients . For example given a @Client(id = "hello-world") you can configure Ribbon settings with:

Per Client Ribbon Settings

ribbon:
    clients:
        hello-world:
            VipAddress: test
            ServerListRefreshInterval: 2000

By default Micronaut registers a DiscoveryClientServerList for each client that integrates Ribbon with Micronaut’sDiscoveryClient.

8.4 Distributed Tracing

When operating Microservices in production it can be challenging to troubleshoot interactions between Microservices in a distributed architecture.

To solve this problem a way to visualize interactions between Microservices in a distributed manner can be critical. Currently, there are various distributed tracing solutions, the most popular of which are Zipkin and Jaeger both of which provide different levels of support for the Open Tracing API.

Micronaut features integration with both Zipkin and Jaeger (via the Open Tracing API).

To enable tracing you should add the tracing module to your build.gradle or pom.xml file:

implementation("io.micronaut:micronaut-tracing")
<dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-tracing</artifactId>
</dependency>

Tracing Annotations

The io.micronaut.tracing.annotation package contains annotations that can be declared on methods to create new spans or continue existing spans.

The available annotations are:

  • The@NewSpan annotation will create a new span, wrapping the method call or reactive type.

  • The@ContinueSpan annotation will continue an existing span, wrapping the method call or reactive type.

  • The@SpanTag annotation can be used on method arguments to include the value of each argument within a Span’s tags. When you use @SpanTag on a method argument, you need either to annotate the method with @NewSpan or @ContinueSpan .

The following snippet presents an example of using the annotations:

Using Trace Annotations

@Singleton
class HelloService {

    @NewSpan("hello-world") <i data-value="1"></i><b>(1)</b>
    public String hello(@SpanTag("person.name") String name) { <i data-value="2"></i><b>(2)</b>
        return greet("Hello " + name);
    }

    @ContinueSpan <i data-value="3"></i><b>(3)</b>
    public String greet(@SpanTag("hello.greeting") String greet) {
        return greet;
    }
}
1 The@NewSpan annotation is used to start a new span
2 You can use@SpanTag to include arguments of methods as tags for the span
3 The@ContinueSpan annotation can be used to continue as existing span and incorporate additional tags using @SpanTag

Tracing Instrumentation

In addition to explicit tracing tags, Micronaut includes a number of instrumentations to ensure that the Span context is propagated between threads and across Microservice boundaries.

These instrumentations are found in the io.micronaut.tracing.instrument package and include HTTPandto propagate the necessary headers via HTTP.

Tracing Beans

If the Tracing annotations and existing instrumentations are not enough, Micronaut’s tracing integration registers a io.opentracing.Tracer bean that can be injected into any class and exposes the Open Tracing API.

Depending on the implementation you choose there are also additional beans. For example for Zipkin brave.Tracing and brave.SpanCustomizer beans are available too.

8.4.1 Tracing with Zipkin

Zipkin is a distributed tracing system. It helps gather timing data needed to troubleshoot latency problems in microservice architectures. It manages both the collection and lookup of this data.

Running Zipkin

The quickest way to get up and started with Zipkin is with Docker:

Running Zipkin with Docker

$ docker run -d -p 9411:9411 openzipkin/zipkin

You can then open a browser tab to the location http://localhost:9411 to view traces.

Sending Traces to Zipkin

Using the CLI

If you are creating your project using the Micronaut CLI, supply the tracing-zipkin feature to include Zipkin tracing in your project:

$ mn create-app my-app --features tracing-zipkin

To send tracing spans to Zipkin the minimal configuration requires you add the following dependencies to build.gradle or pom.xml :

Adding Zipkin Dependencies

runtime 'io.zipkin.brave:brave-instrumentation-http'
runtime 'io.zipkin.reporter2:zipkin-reporter'
compile 'io.opentracing.brave:brave-opentracing'

Then you need to enable ZipKin tracing in your configuration (potentially only your production configuration):

application.yml

tracing:
    zipkin:
        enabled: true
Or alternatively if you have the Micronaut CLI installed you can configure Zipkin when creating your service with: mn create-app hello-world --features tracing-zipkin

Customizing the Zipkin Sender

In order to send spans you need to configure a Zipkin sender. You can configure aHttpClientSender that sends Spans asynchronously using Micronaut’s native HTTP client using the tracing.zipkin.http.url setting:

Configuring Multiple Zipkin Servers

tracing:
    zipkin:
        enabled: true
        http:
            url: http://localhost:9411

It is unlikely that sending spans to localhost will be suitable for production deployment so you generally will want to configure the location of one or many Zipkin servers for production:

Configuring Multiple Zipkin Servers

tracing:
    zipkin:
        enabled: true
        http:
            urls:
                - http://foo:9411
                - http://bar:9411
In production, setting TRACING_ZIPKIN_HTTP_URLS environment variable with a comma separated list of URLs will also work.

Alternatively if you wish to use a different zipkin2.reporter.Sender implementation, you can simply define a bean that is of type zipkin2.reporter.Sender and it will be picked up.

Zipkin Configuration

There are many configuration options available for the Brave client that sends Spans to Zipkin and they are generally exposed via the BraveTracerConfiguration class. You can refer to the javadoc for all the available options.

Below is an example of customizing Zipkin configuration:

Customizing Zipkin Configuration

tracing:
    zipkin:
        enabled: true
        traceId128Bit: true
        sampler:
            probability: 1

You can also optionally dependency inject common configuration classes into BraveTracerConfiguration such as brave.sampler.Sampler just by defining them as beans. See the API for BraveTracerConfiguration for available injection points.

8.4.2 Tracing with Jaeger

Jaeger is another distributed tracing system developed at Uber that is more or less the reference implementation for Open Tracing .

Running Jaeger

The easiest way to get started with Jaeger is to run Jaeger via Docker:

$ docker run -d \
  -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
  -p 5775:5775/udp \
  -p 6831:6831/udp \
  -p 6832:6832/udp \
  -p 5778:5778 \
  -p 16686:16686 \
  -p 14268:14268 \
  -p 9411:9411 \
  jaegertracing/all-in-one:1.6

You can then navigate to http://localhost:16686 to access the Jaeger UI.

See Getting Started with Jaeger for more information.

Sending Traces to Jaeger

Using the CLI

If you are creating your project using the Micronaut CLI, supply the tracing-jaeger feature to include Jaeger tracing in your project:

$ mn create-app my-app --features tracing-jaeger

To send tracing spans to Jaeger the minimal configuration requires you add the following dependencies to build.gradle or pom.xml :

Adding Jaeger Dependencies

compile 'io.jaegertracing:jaeger-thrift:0.31.0'

Then you need to enable Jaeger tracing in your configuration (potentially only your production configuration):

application.yml

tracing:
    jaeger:
        enabled: true

By default Jaeger will be configured to send traces to a locally running Jaeger agent.

Or alternatively if you have the Micronaut CLI installed you can configure Jaeger when creating your service with: mn create-app hello-world --features tracing-jaeger

Jaeger Configuration

There are many configuration options available for the Jaeger client that sends Spans to Jaeger and they are generally exposed via theJaegerConfiguration class. You can refer to the javadoc for all the available options.

Below is an example of customizing JaegerConfiguration configuration:

Customizing Jaeger Configuration

tracing:
    jaeger:
        enabled: true
        sampler:
            probability: 0.5
        sender:
            agentHost: foo
            agentPort: 5775
        reporter:
            flushInterval: 2000
            maxQueueSize: 200

You can also optionally dependency inject common configuration classes intoJaegerConfiguration such as io.jaegertracing.Configuration.SamplerConfiguration just by defining them as beans. See the API forJaegerConfiguration for available injection points.

9 Serverless Functions

Server-less architectures where as a developer you deploy functions that are fully managed by the Cloud environment and are executed in ephemeral processes require a unique approach.

Traditional frameworks like Grails and Spring are not really suitable since low memory consumption and fast startup time are critical, since the Function as a Service (FaaS) server will typically spin up your function for a period using a cold start and then keep it warm.

Micronaut’s compile-time approach, fast startup time and low-memory footprint make it a great candidate for using as a framework for developing functions and in fact Micronaut features dedicated support for developing and deploying functions to AWS Lambda, Google Cloud Function, Azure Function and any FaaS system that supports running functions as containers (such as OpenFaaS, Rift or Fn).

There are generally 2 approaches to writing functions with Micronaut:

  1. Low-level functions that are written using the native API of the function platform

  2. Higher-level functions where you simply define controllers as you would normally do in a typical Micronaut application and deploy to the function platform.

The first approach has marginally less startup time overhead and is typically used for non-HTTP functions such as functions that listen to an event or background functions.

The second approach is only for HTTP functions and is useful for users who want to take a slice of an existing application and deploy it as a serverless function. If cold start performance is a concern it is recommended that you consider building a native image with GraalVM for this option.

9.1 AWS Lambda

Support for AWS Lambda is implemented in the Micronaut AWS subproject.

Simple Functions with AWS Lambda

You can implement AWS Request Handlers with Micronaut that implement directly the AWS Lambda SDK API. See the documentation on Micronaut Request Handlers for more information.

Using the CLI

To create an AWS Lambda Function:

$ mn create-function-app my-app --features aws-lambda

Or with Micronaut Launch

$ curl https://launch.micronaut.io/create/function/example\?features\=aws-lambda -o example.zip
$ unzip example.zip -d example

HTTP Functions with AWS Lambda

You can deploy regular Micronaut applications that use@Controller etc. using Micronaut’s support for AWS API Gateway. See the documentation on AWS API Gateway Support for more information.

Using the CLI

To create an AWS API Gateway Proxy application:

$ mn create-app my-app --features aws-lambda

Or with Micronaut Launch

$ curl https://launch.micronaut.io/example.zip\?features\=aws-lambda -o example.zip
$ unzip example.zip -d example

9.2 Google Cloud Function

Support for Google Cloud Function is implemented in the Micronaut GCP subproject.

Simple Functions with Cloud Function

You can implement Cloud Functions with Micronaut that implement directly the Cloud Function Framework API . See the documentation on Simple Functions for more information.

Using the CLI

To create a Google Cloud Function:

$ mn create-function-app my-app --features google-cloud-function

Or with Micronaut Launch

$ curl https://launch.micronaut.io/create/function/example\?features\=google-cloud-function -o example.zip
$ unzip example.zip -d example

HTTP Functions with Cloud Function

You can deploy regular Micronaut applications that use@Controller etc. using Micronaut’s support for HTTP Functions. See the documentation on Google Cloud HTTP Functions for more information.

Using the CLI

To create a Google Cloud HTTP Function:

$ mn create-app my-app --features google-cloud-function

Or with Micronaut Launch

$ curl https://launch.micronaut.io/example.zip\?features\=google-cloud-function -o example.zip
$ unzip example.zip -d example

9.3 Google CLoud Run

To deploy to Google Cloud Run we recommend using JIB to containerize your application.

Using the CLI

Creating an application with JIB:

$ mn create-app my-app --features jib

Or with Micronaut Launch

$ curl https://launch.micronaut.io/example.zip\?features\=jib -o example.zip
$ unzip example.zip -d example

With JIB setup to deploy your application to Google Container Registry you can simple run:

$ ./gradlew jib

You are now ready to deploy your application :

$ gcloud run deploy --image gcr.io/[PROJECT ID]/example --platform=managed --allow-unauthenticated

Where [PROJECT ID] is replaced for your project ID. You should be asked to specify a region and see output like the following:

Service name: (example):
Deploying container to Cloud Run service [example] in project [PROJECT_ID] region [us-central1]

✓ Deploying... Done.
  ✓ Creating Revision...
  ✓ Routing traffic...
  ✓ Setting IAM Policy...
Done.
Service [example] revision [example-00004] has been deployed and is serving 100 percent of traffic at https://example-9487r97234-uc.a.run.app

The URL is the URL of your Cloud Run application.

9.4 Azure Function

Support for Azure Function is implemented in the Micronaut Azure subproject.

Simple Functions with Azure Function

You can implement Azure Functions with Micronaut that implement directly the Azure Function Java SDK . See the documentation on Azure Functions for more information.

Using the CLI

To create an Azure Function:

$ mn create-function-app my-app --features azure-function

Or with Micronaut Launch

$ curl https://launch.micronaut.io/create/function/example\?features\=azure-function -o example.zip
$ unzip example.zip -d example

HTTP Functions with Azure Function

You can deploy regular Micronaut applications that use@Controller etc. using Micronaut’s support for Azure HTTP Functions. See the documentation on Google Azure HTTP Functions for more information.

Using the CLI

To create a Azure HTTP Function:

$ mn create-app my-app --features azure-function

Or with Micronaut Launch

$ curl https://launch.micronaut.io/example.zip\?features\=azure-function -o example.zip
$ unzip example.zip -d example

10 Message-Driven Microservices

In the past, with monolithic applications, message listeners that listened to messages from messaging systems would frequently be embedded in the same application unit.

In Microservice architectures it is common to have individual Microservice applications that are driven by a message system such as RabbitMQ or Kafka.

In fact a Message-driven Microservice may not even feature an HTTP endpoint or HTTP server (although this can be valuable from a health check and visibility perspective).

10.1 Kafka Support

Apache Kafka is a distributed stream processing platform that can be used for a range of messaging requirements in addition to stream processing and real-time data handling.

Micronaut features dedicated support for defining both Kafka Producer and Consumer instances. Micronaut applications built with Kafka can be deployed with or without the presence of an HTTP server.

With Micronaut’s efficient compile-time AOP and cloud native features, writing efficient Kafka consumer applications that use very little resources is a breeze.

See the documentation for Micronaut Kafka for more information on how to build Kafka applications with Micronaut.

10.2 RabbitMQ Support

RabbitMQ is the most widely deployed open source message broker.

Micronaut features dedicated support for defining both RabbitMQ publishers and consumers. Micronaut applications built with RabbitMQ can be deployed with or without the presence of an HTTP server.

With Micronaut’s efficient compile-time AOP, using RabbitMQ has never been easier. Support has been added for publisher confirms and RPC through reactive streams.

See the documentation for Micronaut RabbitMQ for more information on how to build RabbitMQ applications with Micronaut.

10.3 Nats.io Support

Nats.io is a simple, secure and high performance open source messaging system for cloud native applications, IoT messaging, and microservices architectures.

Micronaut features dedicated support for defining both Nats.io publishers and consumers. Micronaut applications built with Nats.io can be deployed with or without the presence of an HTTP server.

With Micronaut’s efficient compile-time AOP, using Nats.io has never been easier. Support has been added for publisher confirms and RPC through reactive streams.

See the documentation for Micronaut Nats for more information on how to build Nats.io applications with Micronaut.

11 Standalone Command Line Applications

In certain cases you may which to create standalone command-line (CLI) applications that interact with your Microservice infrastructure.

Examples of applications like this include scheduled tasks, batch applications and general command line applications.

In this case having a robust way to parse command line options and positional parameters is important.

11.1 Picocli Support

Picocli is a command line parser that supports usage help with ANSI colors, autocomplete and nested subcommands. It has an annotations API to create command line applications with almost no code, and a programmatic API for dynamic uses like creating Domain Specific Languages.

See the documentation for the Picocli integration for more information.

12 Configurations

Micronaut features several built-in configurations that enable integration with common databases and other servers.

12.1 Configurations for Reactive Programming

RxJava 2 is the default reactive programming library used by Micronaut, however individual modules for other reactive libraries have been added.

12.1.1 RxJava 3 Support

To add support for RxJava 3 add the following module:

implementation("io.micronaut.rxjava3:micronaut-rxjava3")
<dependency>
    <groupId>io.micronaut.rxjava3</groupId>
    <artifactId>micronaut-rxjava3</artifactId>
</dependency>

To use the RxJava 3 HTTP client add the following dependency:

implementation("io.micronaut.rxjava3:micronaut-rxjava3-http-client")
<dependency>
    <groupId>io.micronaut.rxjava3</groupId>
    <artifactId>micronaut-rxjava3-http-client</artifactId>
</dependency>

For more information see the documentation for Micronaut RxJava 3 .

12.1.2 Reactor Support

To add support for Reactor add the following module:

implementation("io.micronaut.reactor:micronaut-reactor")
<dependency>
    <groupId>io.micronaut.reactor</groupId>
    <artifactId>micronaut-reactor</artifactId>
</dependency>

To use the RxJava 3 HTTP client add the following dependency:

implementation("io.micronaut.reactor:micronaut-reactor-http-client")
<dependency>
    <groupId>io.micronaut.reactor</groupId>
    <artifactId>micronaut-reactor-http-client</artifactId>
</dependency>

For more information see the documentation for Micronaut Reactor .

12.1.3 RxJava 1 Support

Legacy support for RxJava 1 can be added with the following module:

implementation("io.micronaut.rxjava1:micronaut-rxjava1")
<dependency>
    <groupId>io.micronaut.rxjava1</groupId>
    <artifactId>micronaut-rxjava1</artifactId>
</dependency>

For more information see the Micronaut RxJava1 documentation.

12.2 Configurations for Data Access

The table summarizes the configuration modules and the dependencies you should add to your build to enable them.

Table 1. Data Access Configuration Modules
Dependency Description

io.micronaut.sql:micronaut-jdbc-dbcp

Configures SQL DataSource instances using Commons DBCP

io.micronaut.sql:micronaut-jdbc-hikari

Configures SQL DataSource instances using Hikari Connection Pool

io.micronaut.sql:micronaut-jdbc-tomcat

Configures SQL DataSource instances using Tomcat Connection Pool

io.micronaut.sql:micronaut-hibernate-jpa

Configures Hibernate/JPA EntityManagerFactory beans

io.micronaut.configuration:micronaut-hibernate-gorm

Configures GORM for Hibernate for Groovy applications

io.micronaut.mongodb:micronaut-mongo-reactive

Configures the MongoDB Reactive Driver

io.micronaut.configuration:micronaut-mongo-gorm

Configures GORM for MongoDB for Groovy applications

io.micronaut.neo4j:micronaut-neo4j-bolt

Configures the Bolt Java Driver for Neo4j

io.micronaut.configuration:micronaut-neo4j-gorm

Configures GORM for Neo4j for Groovy applications

io.micronaut.sql:micronaut-vertx-mysql-client

Configures the Reactive MySQL Client

io.micronaut.sql:micronaut-vertx-pg-client

Configures the Reactive Postgres Client

io.micronaut.redis:micronaut-redis-lettuce

Configures the Lettuce driver for Redis

io.micronaut.cassandra:micronaut-cassandra

Configures the Datastax Java Driver for Cassandra

For example, to add support for MongoDB you define the following dependency:

build.gradle

compile "io.micronaut.mongodb:micronaut-mongo-reactive"

For Groovy users, Micronaut provides special support for GORM .

For GORM for Hibernate you should not have both the hibernate-jpa and hibernate-gorm dependency.

The following sections go into more detail about configuration options and the exposed beans for each implementation.

12.2.1 Configuring a SQL Data Source

Java data sources can be configured for one of three currently provided implementations. Apache DBCP2, Hikari, and Tomcat are supported by default.

Configuring a JDBC DataSource

Using the CLI

If you are creating your project using the Micronaut CLI, supply one of the jdbc-tomcat , jdbc-hikari , or jdbc-dbcp features to preconfigure a simple JDBC connection in your project, along with a default H2 database driver:

$ mn create-app my-app --features jdbc-tomcat

To get started, simply add a dependency to one of the JDBC configurations that corresponds to the implementation you would like to use. Choose one of the following:

runtime("io.micronaut.sql:micronaut-jdbc-tomcat")
<dependency>
    <groupId>io.micronaut.sql</groupId>
    <artifactId>micronaut-jdbc-tomcat</artifactId>
    <scope>runtime</scope>
</dependency>
runtime("io.micronaut.sql:micronaut-jdbc-hikari")
<dependency>
    <groupId>io.micronaut.sql</groupId>
    <artifactId>micronaut-jdbc-hikari</artifactId>
    <scope>runtime</scope>
</dependency>
runtime("io.micronaut.sql:micronaut-jdbc-dbcp")
<dependency>
    <groupId>io.micronaut.sql</groupId>
    <artifactId>micronaut-jdbc-dbcp</artifactId>
    <scope>runtime</scope>
</dependency>
runtime("io.micronaut.sql:micronaut-jdbc-ucp")
<dependency>
    <groupId>io.micronaut.sql</groupId>
    <artifactId>micronaut-jdbc-ucp</artifactId>
    <scope>runtime</scope>
</dependency>

You also need to add a JDBC driver dependency to your classpath. For example to add the H2 In-Memory Database :

runtime("com.h2database:h2")
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

For more information see the Configuring JDBC section of the Micronaut SQL libraries project.

12.2.2 Configuring Hibernate

Setting up a Hibernate/JPA EntityManager

Using the CLI

If you are creating your project using the Micronaut CLI, supply the hibernate-jpa feature to include a Hibernate JPA configuration in your project:

$ mn create-app my-app --features hibernate-jpa

Micronaut features built in support for configuring a Hibernate / JPA EntityManager that builds on theSQL DataSource support.

Once you haveconfigured one or many DataSourcesto use Hibernate, you will need to add the hibernate-jpa dependency to your build configuration:

implementation("io.micronaut.sql:micronaut-hibernate-jpa")
<dependency>
    <groupId>io.micronaut.sql</groupId>
    <artifactId>micronaut-hibernate-jpa</artifactId>
</dependency>

For more information see the Configuring Hibernate section of the Micronaut SQL libraries project.

Using GORM for Hibernate

For Groovy users and users familiar with the Grails framework, special support for GORM for Hibernate is available. To use GORM for Hibernate you should not include Micronaut’s built inor the hibernate-jpa dependency since GORM itself takes responsibility for creating the DataSource , SessionFactory etc.

Using the CLI

If you are creating your project using the Micronaut CLI, supply the hibernate-gorm feature to include GORM, a basic connection pool configuration, and a default H2 database driver in your project:

$ mn create-app my-app --features hibernate-gorm

See the GORM Modules section of the Micronaut for Groovy user guide.

12.2.3 Configuring MongoDB

Setting up the Native MongoDB Driver

Using the CLI

If you are creating your project using the Micronaut CLI, supply the mongo-reactive feature to configure the native MongoDB driver in your project:

$ mn create-app my-app --features mongo-reactive

Micronaut includes a configuration to automatically configure the native MongoDB Java driver. To use this configuration, add the following dependency to your application:

implementation("io.micronaut.mongodb:micronaut-mongo-reactive")
<dependency>
    <groupId>io.micronaut.mongodb</groupId>
    <artifactId>micronaut-mongo-reactive</artifactId>
</dependency>

Then configure the URI of the MongoDB server in application.yml :

Configuring a MongoDB server

mongodb:
    uri: mongodb://username:password@localhost:27017/databaseName
The mongodb.uri follows the MongoDB Connection String format.

A non-blocking Reactive Streams MongoClient is then available for dependency injection.

To use the blocking driver, add a dependency to your application to the mongo-java-driver.

compile "org.mongodb:mongo-java-driver"

Then the blocking MongoClient will be available for injection.

See the Micronaut MongoDB documentation for further information on configuring and using MongoDB within Micronaut.

12.2.4 Configuring Neo4j

Micronaut features dedicated support for automatically configuring the Neo4j Bolt Driver for the popular Neo4j Graph Database.

Using the CLI

If you are creating your project using the Micronaut CLI, supply the neo4j-bolt feature to configure the Neo4j Bolt driver in your project:

$ mn create-app my-app --features neo4j-bolt

To configure the Neo4j Bolt driver you should first add the neo4j-bolt module to your classpath:

implementation("io.micronaut:micronaut-neo4j-bolt")
<dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-neo4j-bolt</artifactId>
</dependency>

You should then configure the URI of the Neo4j server you wish to communicate with in application.yml :

Configuring neo4j.uri
neo4j:
    uri: bolt://localhost
The neo4j.uri setting should be in the format as described in the Connection URIs section of the Neo4j documentation

Once you have the above configuration in place you can inject an instance of the org.neo4j.driver.v1.Driver bean, which features both a synchronous blocking API and a non-blocking API based on CompletableFuture .

See the Micronaut Neo4j documentation for further information on configuring and using Neo4j within Micronaut.

12.2.5 Configuring Postgres

Micronaut supports reactive and non-blocking client to connect to Postgres using vertx-pg-client , allowing to handle many database connections with a single thread.

Configuring the Reactive Postgres Client

Using the CLI

If you are creating your project using the Micronaut CLI, supply the vertx-pg-client feature to configure the Reactive Postgres client in your project:

$ mn create-app my-app --features vertx-pg-client

To configure the Reactive Postgres client you should first add vertx-pg-client module to your classpath:

build.gradle

compile "io.micronaut.sql:micronaut-vertx-pg-client"

For more information see the Configuring Reactive Postgres section of the Micronaut SQL libraries project.

12.2.6 Configuring Redis

Micronaut features automatic configuration of the Lettuce driver for Redis via the redis-lettuce module.

Configuring Lettuce

Using the CLI

If you are creating your project using the Micronaut CLI, supply the redis-lettuce feature to configure the Lettuce driver in your project:

$ mn create-app my-app --features redis-lettuce

To configure the Lettuce driver you should first add the redis-lettuce module to your classpath:

build.gradle

compile "io.micronaut.redis:micronaut-redis-lettuce"

You should then configure the URI of the Redis server you wish to communicate with in application.yml :

Configuring redis.uri
redis:
    uri: redis://localhost
The redis.uri setting should be in the format as described in the Connection URIs section of the Lettuce wiki

You can also specify multiple Redis URIs using redis.uris in which case a RedisClusterClient is created instead.

For more information and further documentation see the Micronaut Redis documentation.

12.2.7 Configuring Cassandra

Using the CLI

If you are creating your project using the Micronaut CLI, supply the cassandra feature to include Cassandra configuration in your project:

$ mn create-app my-app --features cassandra

For more information see the Micronaut Cassandra Module documentation.

12.2.8 Configuring Liquibase

To configure Micronaut integration with Liquibase , please follow these instructions .

12.2.9 Configuring Flyway

To configure Micronaut integration with Flyway , please follow these instructions

13 Language Support

Micronaut supports any JVM language that implements the Java Annotation Processor API.

Although Groovy, does not in fact support this API, special support has been built via an AST transformation. The current list of supported languages is: Java, Groovy and Kotlin (via the kapt tool).

Theoretically any language that supports a way to analyze the AST at compile time could be supported. The io.micronaut.inject.writer package includes classes that are language neutral and are used to buildBeanDefinition classes at compile time using the ASM tool.

The following sections cover language specific features and considerations for using Micronaut.

13.1 Micronaut for Java

For Java, Micronaut uses a Java BeanDefinitionInjectProcessor annotation processor to process classes at compile time and produceBeanDefinition classes.

The major advantage here is that you pay a slight cost at compile time, but at runtime Micronaut is largely reflection-free, fast and consumes very little memory.

13.1.1 Using Micronaut with Java 9+

Micronaut is built with Java 8, however works fine with Java 9/10 and above as well. The classes that Micronaut generates sit alongside existing classes within the same package, hence do not violate anything regarding the Java module system.

There are some considerations to take into account when using Java 9/10 with Micronaut.

The javax.annotation package

Using the CLI

If you are creating your project using the Micronaut CLI, the javax.annotation dependency will be added to your project automatically if you are using Java 9+.

The javax.annotation , which includes @PostConstruct , @PreDestroy etc. is no longer part of the core JDK but instead a module. If you run under Java 9+ you will need to import the module or add the dependency to your classpath:

Adding the javax.annotation dependency
implementation 'javax.annotation:javax.annotation-api:1.3.2'

13.1.2 Incremental Annotation Processing with Gradle

Micronaut supports Gradle incremental annotation processing which helps speed up builds by compiling only classes that have changed avoiding a full recompilation.

However, the support is disabled by default since Micronaut allows the definition of custom meta-annotations (to for example define) that need to be configured for processing.

The following example demonstrates how to enable and configure incremental annotation processing for annotations you have defined under the com.example package:

Enabling Incremental Annotation Processing

tasks.withType(JavaCompile) {
    options.compilerArgs = [
        '-Amicronaut.processing.incremental=true',
        '-Amicronaut.processing.annotations=com.example.*',
    ]
}
If you do not enable processing for custom annotations you define these annotations will simple be ignored by Micronaut which may break your application.

13.1.3 Using Project Lombok

Project Lombok is a popular java library that adds a number of useful AST transformations to the Java language via annotation processors.

Since both Micronaut and Lombok use annotation processors, special care needs to be taken when configuring Lombok to ensure that the Lombok processor runs before Micronaut’s processor.

For example in Gradle adding the following dependencies to the dependencies block:

Configuring Lombok in Gradle

compileOnly 'org.projectlombok:lombok:1.16.20'
annotationProcessor "org.projectlombok:lombok:1.16.20"
...
// Micronaut processor define after Lombok
annotationProcessor "io.micronaut:micronaut-inject-java"

Or when using Maven:

Configuring Lombok in Maven

<annotationProcessorPaths>
  <path>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.0</version>
  </path>
  <path>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-inject-java</artifactId>
    <version>${micronaut.version}</version>
  </path>
</annotationProcessorPaths>
In both cases (Gradle and Maven) the Micronaut processor should be configured after the Lombok processor, reversing the order of the declared dependencies will not work.

13.1.4 Configuring an IDE

You can use any IDE to develop Micronaut applications, if you depend on your configured build tool (Gradle or Maven) to build the application.

However, if you wish to run tests within the IDE that is currently possible with IntelliJ IDEA or Eclipse 4.9 M2 or above.

See the section onin the Quick start for more information on how to configure IntelliJ or Eclipse.

13.1.5 Retaining Parameter Names

By default with Java, the parameter name data for method parameters is not retained at compile time. This can be a problem for Micronaut if you do not define parameter names explicitly and depend on an external JAR that is already compiled.

Consider this interface:

Client Interface

interface HelloOperations {
    @Get("/hello/{name}")
    String hello(String name);
}

At compile time the parameter name name is lost and becomes simply arg0 when compiled against or read via reflection later. To avoid this problem you have two options. You can either declare the parameter name explicitly:

Client Interface

interface HelloOperations {
    @Get("/hello/{name}")
    String hello(@QueryValue("name") String name);
}

Or alternatively it is recommended that you compile all byte code with -parameters flag to javac . See Obtaining Names of Method Parameters . For example in build.gradle :

build.gradle

compileJava.options.compilerArgs += '-parameters'

13.2 Micronaut for Groovy

The Groovy language has first class support in Micronaut.

Groovy Specific Modules

Additional modules exist specific to Groovy that improve the overall experience. These are detailed in the table below:

Table 1. Groovy Specific Modules
Dependency Description

io.micronaut:micronaut-inject-groovy

Includes AST transformations to generate bean definitions. Should be compileOnly on your classpath.

io.micronaut:micronaut-runtime-groovy

Adds the ability to specify configuration placed in src/main/resources in Groovy format (i.e. application.groovy )

io.micronaut:micronaut-function-groovy

Includes AST transforms that make it easier to writefor AWS Lambda

The most common module you will need is micronaut-inject-groovy , which enables DI and AOP for classes written in Groovy.

Groovy Support in the CLI

TheCommand Line Interfacefor Micronaut includes special support for Groovy. To create a Groovy application use the groovy lang option. For example:

Create a Micronaut Groovy application

$ mn create-app hello-world --lang groovy

The above will generate a Groovy project, built with Gradle. You can use the -build maven flag to generate a project built with Maven instead.

Once you have created an application with the groovy feature commands like create-controller , create-client etc. will generate Groovy files instead of Java. The following example demonstrates this action when using interactive mode of the CLI:

Create a bean

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

create-bean          create-client        create-controller
create-job           help

mn> create-bean helloBean
| Rendered template Bean.groovy to destination src/main/groovy/hello/world/HelloBean.groovy

The above example demonstrates creating a Groovy bean that looks like the following:

Micronaut Bean

package hello.world

import javax.inject.Singleton

@Singleton
class HelloBean {

}
Groovy automatically imports groovy.lang.Singleton which can be confusing as it conflicts with javax.inject.Singleton . Make sure you use javax.inject.Singleton when declaring a Micronaut singleton bean to avoid surprising behavior.

We can also create a client - don’t forget Micronaut can act as a client or a server!

Create a client

mn> create-client helloClient
| Rendered template Client.groovy to destination src/main/groovy/hello/world/HelloClient.groovy

Micronaut Client

package hello.world

import io.micronaut.http.client.annotation.Client
import io.micronaut.http.annotation.Get
import io.micronaut.http.HttpStatus

@Client("hello")
interface HelloClient {

    @Get
    HttpStatus index()
}

Now let’s create a controller:

Create a controller

mn> create-controller helloController
| Rendered template Controller.groovy to destination src/main/groovy/hello/world/HelloController.groovy
| Rendered template ControllerSpec.groovy to destination src/test/groovy/hello/world/HelloControllerSpec.groovy
mn>

Micronaut Controller

package hello.world

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


@Controller("/hello")
class HelloController {

    @Get
    HttpStatus index() {
        return HttpStatus.OK
    }
}

As you can see from the output from the CLI a Spock test was also generated for you demonstrating how to test the controller:

HelloControllerSpec.groovy

...
    void "test index"() {
        given:
        HttpResponse response = client.toBlocking().exchange("/hello")

        expect:
        response.status == HttpStatus.OK
    }
...

Notice how you use Micronaut both as client and as a server to test itself.

Programmatic Routes with GroovyRouterBuilder

If you prefer to build your routes programmatically (similar to Grails UrlMappings ) then a special io.micronaut.web.router.GroovyRouteBuilder exists that has some enhancements to make the DSL better.

The following example shows GroovyRouteBuilder in act:

Using GroovyRouteBuilder

@Singleton
static class MyRoutes extends GroovyRouteBuilder {

    MyRoutes(ApplicationContext beanContext) {
        super(beanContext)
    }

    @Inject
    void bookResources(BookController bookController, AuthorController authorController) {
        GET(bookController) {
            POST("/hello{/message}", bookController.&hello) <i data-value="1"></i><b>(1)</b>
        }
        GET(bookController, ID) { <i data-value="2"></i><b>(2)</b>
            GET(authorController)
        }
    }
}
1 You can use injected controllers to create routes by convention and Groovy method references to create routes to methods
2 The ID property can be used to reference include an {id} URI variable

The above example results in the following routes:

  • /book - Maps to BookController.index()

  • /book/hello/{message} - Maps to BookController.hello(String)

  • /book/{id} - Maps to BookController.show(String id)

  • /book/{id}/author - Maps to AuthorController.index

Using GORM in a Groovy application

GORM is a data access toolkit originally created as part of Grails framework. It supports multiple database types. The following table summarizes the modules needed to use GORM and links to documentation.

Table 2. GORM Modules
Dependency Description

io.micronaut.configuration:micronaut-hibernate-gorm

Configures GORM for Hibernate for Groovy applications. See thedocs

io.micronaut.configuration:micronaut-mongo-gorm

Configures GORM for MongoDB for Groovy applications. See thedocs.

io.micronaut.configuration:micronaut-neo4j-gorm

Configures GORM for Neo4j for Groovy applications. See thedocs.

Once you have configured a GORM implementation per the instructions linked in the table above you can use all features of GORM.

GORM Data Services can also participate in dependency injection and life cycle methods:

GORM Data Service VehicleService.groovy

@Service(Vehicle)
abstract class VehicleService {
    @PostConstruct
    void init() {
       // do something on initialization

    }

    abstract Vehicle findVehicle(@NotBlank String name)

    abstract Vehicle saveVehicle(@NotBlank String name)

}

You can also define the service as an interface instead of an abstract class if you want GORM to do all of the work and you don’t want to add your own behaviors.

Serverless Functions with Groovy

A microservice application is just one way to use Micronaut. You can also use it for serverless functions like on AWS Lambda.

With the function-groovy module, Micronaut features enhanced support for functions written in Groovy.

See the section onfor more information.

13.3 Micronaut for Kotlin

TheCommand Line Interfacefor Micronaut includes special support for Kotlin. To create a Kotlin application use the kotlin lang option. For example:

Create a Micronaut Kotlin application

$ mn create-app hello-world --lang kotlin

Support for Kotlin in Micronaut is built upon the Kapt compiler plugin, which includes support for Java annotation processors. To use Kotlin in your Micronaut application, you will simply need to add the proper dependencies to configure and run kapt on your kt source files. Kapt will create Java "stub" classes for each of your Kotlin classes, which can then be processed by Micronaut’s Java annotation processor. The stubs are not included in the final compiled application.

Learn more about kapt and its features from the official documentation.

The Micronaut annotation processors are declared in the kapt scope when using Gradle. For example:

Example build.gradle

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion" <i data-value="1"></i><b>(1)</b>
    compile "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
    kapt "io.micronaut:micronaut-inject-java" <i data-value="2"></i><b>(2)</b>

    kaptTest "io.micronaut:micronaut-inject-java" <i data-value="3"></i><b>(3)</b>
    ...
}
1 Add the Kotlin standard libraries
2 Add the micronaut-inject-java dependency under the kapt scope, so classes in src/main are processed
3 Add the micronaut-inject-java dependency under the kaptTest scope, so classes in src/test are processed.

With a build.gradle file similar to the above, you can now run your Micronaut application using the run task (provided by the Application plugin):

$ ./gradlew run

An example controller written in Kotlin can be seen below:

src/main/kotlin/example/HelloController.kt

package example

import io.micronaut.http.annotation.*

@Controller("/")
class HelloController {

    @Get("/hello/{name}")
    fun hello(name: String): String {
        return "Hello $name"
    }
}

13.3.1 Kotlin, Kapt and IntelliJ

As of this writing IntelliJ’s built-in compiler does not directly support Kapt and annotation processing. You must instead configure Intellij to run Gradle (or Maven) compilation as a build step before running your tests or application class.

First edit the run configuration for tests or for the application and select "Run Gradle task" as a build step:

Micronaut 2.0: a full stack Java framework for modular, testable applications

Then add the classes task as task to execute for the application or for tests the testClasses task:

Micronaut 2.0: a full stack Java framework for modular, testable applications

Now whenever you run tests or the application Micronaut classes will be generated at compilation time.

Alternatively, you can delegate IntelliJ build/run actions to Gradle completely:

Micronaut 2.0: a full stack Java framework for modular, testable applications

13.3.2 Incremental Annotation Processing with Gradle and Kapt

To enable gradle incremental annotation processing with Kapt the arguments as specifiedIncremental Annotation Processing with Gradlemust be sent to Kapt

The following example demonstrates how to enable and configure incremental annotation processing for annotations you have defined under the com.example and io.example packages:

Enabling Incremental Annotation Processing in Kapt

kapt {
    arguments {
        arg("micronaut.processing.incremental", true)
        arg("micronaut.processing.annotations", "com.example.*,io.example.*")
    }
}
If you do not enable processing for custom annotations you define these annotations will simple be ignored by Micronaut which may break your application.

13.3.3 Kotlin and AOP Advice

Micronaut provides a compile-time AOP API that does not use reflection. When you use any Micronaut’s, it creates a subclass at compile-time to provide the AOP behaviour. This can be a problem because Kotlin classes are final by default. If the application was created with the Micronaut CLI, then the Kotlin all-open plugin is configured for you to automatically change your classes to open when an AOP annotation is used. To configure it yourself, simply add theAround class to the list of supported annotations.

If you prefer not to or cannot use the all-open plugin, you must declare the classes that are annotated with an AOP annotation to be open:

import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.HttpStatus
import io.micronaut.validation.Validated
import javax.validation.constraints.NotBlank

@Validated
@Controller("/email")
open class EmailController { <i data-value="1"></i><b>(1)</b>

    @Get("/send")
    fun index(@NotBlank recipient: String, <i data-value="1"></i><b>(1)</b>
                   @NotBlank subject: String): HttpStatus {
        return HttpStatus.OK
    }
}
1 if you use @Validated AOP Advice, you need to use open at class and method level.
The all-open plugin does not handle methods. If you declare an AOP annotation on a method, you must manually declare it as open.

13.3.4 Kotlin and Retaining Parameter Names

Like with Java, the parameter name data for method parameters is not retained at compile time when using Kotlin. This can be a problem for Micronaut if you do not define parameter names explicitly and depend on an external JAR that is already compiled.

To enable the retention of parameter name data with Kotlin you should set the javaParameters option to true in your build.gradle :

build.gradle

compileTestKotlin {
	kotlinOptions {
	    jvmTarget = '1.8'
	    javaParameters = true
	}
}

13.3.5 Coroutines Support

Kotlin coroutines allow you to create asynchronous applications with imperative style code. A Micronaut’s controller action can be a suspend function:

Controller suspend function example

@Get("/simple")
    suspend fun simple(): String { <b>(1)</b>
        return "Hello"
    }
1 The function is marked as suspend , though in reality it won’t be suspended.

Controller suspend function example

@Get("/delayed")
    suspend fun delayed(): String { <b>(1)</b>
        delay(1) <b>(2)</b>
        return "Delayed"
    }
1 The function is marked as suspend .
2 The delay is called to make sure that a function is suspended and the response is returned from a different thread.

Controller suspend function example

@Status(HttpStatus.CREATED) <b>(1)</b>
    @Get("/status")
    suspend fun status(): Unit {
    }
1 suspend function also works when all we want is to return a status.

Controller suspend function example

@Status(HttpStatus.CREATED)
    @Get("/statusDelayed")
    suspend fun statusDelayed(): Unit {
        delay(1)
    }

You can also use Flow type for streaming server and client. A streaming controller can return Flow , for example:

Streaming JSON on the Server with Flow

@Get(value = "/headlinesWithFlow", processes = [MediaType.APPLICATION_JSON_STREAM])
    internal fun streamHeadlinesWithFlow(): Flow<Headline> = <b>(1)</b>
        flow { <b>(2)</b>
            repeat(100) { <b>(3)</b>
                with (Headline()) {
                    text = "Latest Headline at " + ZonedDateTime.now()
                    emit(this) <b>(4)</b>
                    delay(1_000) <b>(5)</b>
                }
            }
        }
1 A method streamHeadlinesWithFlow is defined that produces application/x-json-stream
2 A Flow is created using flow
3 This Flow will emit 100 messages
4 Emitting will happend with emit suspend function
5 There will be a 1 second delay between messages

A streaming client can simply return a Flow , for example:

Streaming client with Flow

import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Get
import io.micronaut.http.client.annotation.Client
import kotlinx.coroutines.flow.Flow


@Client("/streaming")
interface HeadlineFlowClient {

    @Get(value = "/headlinesWithFlow", processes = [MediaType.APPLICATION_JSON_STREAM]) <b>(1)</b>
    fun streamFlow(): Flow<Headline> <b>(2)</b>
}
1 The @Get method is defined as processing responses of type APPLICATION_JSON_STREAM
2 A Flow is used as the return type

13.4 Micronaut for GraalVM

GraalVM is a new universal virtual machine from Oracle that supports a polyglot runtime environment and the ability to compile Java applications down to native machine code.

Any Micronaut application can be run using the GraalVM JVM, however special support has been added to Micronaut to support running Micronaut applications using GraalVM’s native-image tool .

Micronaut currently supports GraalVM 20.1.0 version and the team is improving the support in every new release. Don’t hesitate to report issues however if you find any problem.

A lot of Micronaut’s modules and third party libraries have been verified to work with GraalVM: HTTP server, HTTP client, Function support, Micronaut Data JDBC and JPA, Service Discovery, RabbitMQ, Views, Security, Zipkin,…​ Support for other modules is still evolving and will improve over time.

Getting Started

Use of GraalVM’s native-image tool is only supported in Java or Kotlin projects. Groovy relies heavily on reflection which is only partially supported by GraalVM.

To start using GraalVM you should first install the GraalVM SDK via the Getting Started or using Sdkman! .

13.4.1 Microservices as GraalVM native images

Getting Started with Micronaut and Graal

To get started creating a Microservice that can be compiled into a native image, use the graalvm feature when creating the application with the CLI:

Creating a Graal Native Microservice

$ mn create-app hello-world --features graalvm

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:

Args = -H:Name=example \ <i data-value="1"></i><b>(1)</b>
       -H:Class=example.Application <i data-value="2"></i><b>(2)</b>
1 The -H:Name argument specifies the name of the native image that will be generated.
2 The -H:Class argument specifies the entry point of your application (the class that defines a static void main method.

Building a Native Image Using Docker

To build your native image using Docker simply run:

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

Or use the provided script:

$ ./gradlew assemble # or ./mvnw package if using Maven
$ ./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.

  2. A typical dockerfile structure will build the final image which is smaller due to layering.

Building a Native Image Without Using Docker

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

Installing GraalVM 20.1.0 with SDKman

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

The native-image 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
$ gu install native-image

Creating native image with Gradle

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

Creating native image with Maven

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

Run native image

$ ./hello-world

Understanding Micronaut and Graal

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:

annotationProcessor("io.micronaut:micronaut-graal")
<annotationProcessorPaths>
    <path>
        <groupId>io.micronaut</groupId>
        <artifactId>micronaut-graal</artifactId>
    </path>
</annotationProcessorPaths>

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@Introspected 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.

For example the following class:

package example;
import io.micronaut.core.annotation.*;

@Introspected
class Test {
    ...
}

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@ReflectiveAccess instead which can be present on any constructor, field or method to include only the specific field, constructor or method.

If you wish to provide your own reflect.json you can add one to src/main/graal/reflect.json and it will be automatically picked up.

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.

Adding Additional Classes for Reflective Access

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@TypeHint.

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:

@TypeHint(
        value = { <i data-value="1"></i><b>(1)</b>
                PropertyNamingStrategy.UpperCamelCaseStrategy.class,
                ArrayList.class,
                LinkedHashMap.class,
                HashSet.class
        },
        accessType = TypeHint.AccessType.ALL_DECLARED_CONSTRUCTORS <i data-value="2"></i><b>(2)</b>
)
1 The value member is used to specify which classes require reflection.
2 The accessType member specifies if only classloading access is needed or whether full reflection on all public members is needed.

Generating Native Images

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
native-image --no-server \ <i data-value="1"></i><b>(1)</b>
             --class-path build/libs/hello-world-0.1-all.jar <i data-value="2"></i><b>(2)</b>
1 Do not start a background server to generate the native image
2 The class-path argument is used to refer to the Micronaut shaded JAR

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

Running the Native Application

$ ./hello-world
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).

13.4.2 GraalVM and Micronaut FAQ

How does Micronaut manage to run on GraalVM?

Micronaut features a Dependency Injection and Aspect-Oriented Programming runtime that uses no reflection. This makes it easier for Micronaut applications to run on GraalVM since there are limitations particularly around reflection on SubstrateVM.

How can I make a Micronaut application that uses picocli run on GraalVM?

Picocli provides a picocli-codegen module with a tool for generating a GraalVM reflection configuration file. The tool can be run manually or automatically as part of the build. The module’s README has usage instructions with code snippets for configuring Gradle and Maven to generate a cli-reflect.json file automatically as part of the build. Add the generated file to the -H:ReflectionConfigurationFiles option when running the native-image tool.

What about other Third-Party Libraries?

Micronaut cannot guarantee that third-party libraries work on GraalVM SubstrateVM, that is down to each individual library to implement support.

I Get a "Class XXX is instantiated reflectively…​" Exception. What do I do?

If you get an error such as:

Class myclass.Foo[] is instantiated reflectively but was never registered. Register the class by using org.graalvm.nativeimage.RuntimeReflection

You may need to manually tweak the generated reflect.json file. For regular classes you need to add an entry into the array:

[
    {
        "name" : "myclass.Foo",
        "allDeclaredConstructors" : true
    },
    ...
]

For arrays this needs to use the Java JVM internal array representation. For example:

[
    {
        "name" : "[Lmyclass.Foo;",
        "allDeclaredConstructors" : true
    },
    ...
]

14 Management & Monitoring

Using the CLI

If you are creating your project using the Micronaut CLI, supply the management feature to configure the management endpoints in your project:

$ mn create-app my-app --features management

Inspired by Spring Boot and Grails, the Micronaut management dependency adds support for monitoring of your application via endpoints : special URIs that return details about the health and state of your application. The management endpoints are also integrated with Micronaut’s security dependency, allowing for sensitive data to be restricted to authenticated users in your security system (see Built-in Endpoints Access in the Security section).

To use the management features described in this section, add the dependency on your classpath.

implementation("io.micronaut:micronaut-management")
<dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-management</artifactId>
</dependency>

14.1 Creating Endpoints

In addition to the, the management dependency also provides support for creating custom endpoints. Custom endpoints can be enabled and configured identically to the built-in endpoints, and can be used to retrieve and return any metrics or other application data that you require.

14.1.1 The Endpoint Annotation

An Endpoint can be created by annotating a class with theEndpoint annotation, and supplying it with (at minimum) an endpoint id.

FooEndpoint.java

@Endpoint("foo")
class FooEndpoint {
    ...
}

If a single String argument is supplied to the annotation, it will be used as the endpoint id.

It is possible to supply additional (named) arguments to the annotation. Other possible arguments to @Endpoint are described in the table below:

Table 1. Endpoint Arguments
Argument Description Endpoint Example

String id

The endpoint id (or name)

@Endpoint(id = "foo")

String prefix

Prefix used for configuring the endpoint (seeEndpoint Configuration)

@Endpoint(prefix = "foo")

boolean defaultEnabled

Sets whether the endpoint is enabled when no configuration is set (seeEndpoint Configuration)

@Endpoint(defaultEnabled = false)

boolean defaultSensitive

Sets whether the endpoint is sensitive if no configuration is set (seeEndpoint Configuration)

@Endpoint(defaultSensitive = false)

Example of custom Endpoint

The following example Endpoint class will create an endpoint accessible at /date :

CurrentDateEndpoint

import io.micronaut.management.endpoint.annotation.Endpoint;

@Endpoint(id = "date",
        prefix = "custom",
        defaultEnabled = true,
        defaultSensitive = false)
public class CurrentDateEndpoint {

    //.. endpoint methods

}

CurrentDateEndpoint

import io.micronaut.management.endpoint.annotation.Endpoint

@Endpoint(id = "date",
        prefix = "custom",
        defaultEnabled = true,
        defaultSensitive = false)
class CurrentDateEndpoint {

    //.. endpoint methods

}

CurrentDateEndpoint

import io.micronaut.management.endpoint.annotation.Endpoint

@Endpoint(id = "date", prefix = "custom", defaultEnabled = true, defaultSensitive = false)
class CurrentDateEndpoint {

    //.. endpoint methods

}

14.1.2 Endpoint Methods

Endpoints respond to GET ("read"), POST ("write") and DELETE ("delete") requests. To return a response from an endpoint, annotate its public method/s with one of following annotations:

Table 1. Endpoint Method Annotations
Annotation Description

Read

Responds to GET requests

Write

Responds to POST requests

Delete

Responds to DELETE requests

Read Methods

Annotating a method with theRead annotation will cause it to respond to GET requests.

CurrentDateEndpoint

import io.micronaut.management.endpoint.annotation.Endpoint;

import io.micronaut.management.endpoint.annotation.Read;

@Endpoint(id = "date",
        prefix = "custom",
        defaultEnabled = true,
        defaultSensitive = false)
public class CurrentDateEndpoint {

    private Date currentDate;

    @Read
    public Date currentDate() {
        return currentDate;
    }

}

CurrentDateEndpoint

import io.micronaut.management.endpoint.annotation.Endpoint

import io.micronaut.management.endpoint.annotation.Read

@Endpoint(id = "date",
        prefix = "custom",
        defaultEnabled = true,
        defaultSensitive = false)
class CurrentDateEndpoint {

    Date currentDate

    @Read
    Date currentDate() {
        return currentDate
    }

}

CurrentDateEndpoint

import io.micronaut.management.endpoint.annotation.Endpoint

import io.micronaut.management.endpoint.annotation.Read

@Endpoint(id = "date", prefix = "custom", defaultEnabled = true, defaultSensitive = false)
class CurrentDateEndpoint {

    private var currentDate: Date? = null

    @Read
    fun currentDate(): Date? {
        return currentDate
    }

}

The above method responds to the following request:

$ curl -X GET localhost:55838/date

1526085903689

TheRead annotation accepts an optional produces argument, which sets the media type returned from the method (default is application/json ):

CurrentDateEndpoint

import io.micronaut.management.endpoint.annotation.Endpoint;

import io.micronaut.management.endpoint.annotation.Read;

import io.micronaut.http.MediaType;
import io.micronaut.management.endpoint.annotation.Selector;

@Endpoint(id = "date",
        prefix = "custom",
        defaultEnabled = true,
        defaultSensitive = false)
public class CurrentDateEndpoint {

    private Date currentDate;

    @Read(produces = MediaType.TEXT_PLAIN) <b>(1)</b>
    public String currentDatePrefix(@Selector String prefix) {
        return prefix + ": " + currentDate;
    }

}

CurrentDateEndpoint

import io.micronaut.management.endpoint.annotation.Endpoint

import io.micronaut.management.endpoint.annotation.Read

import io.micronaut.http.MediaType
import io.micronaut.management.endpoint.annotation.Selector


@Endpoint(id = "date",
        prefix = "custom",
        defaultEnabled = true,
        defaultSensitive = false)
class CurrentDateEndpoint {

    Date currentDate

    @Read(produces = MediaType.TEXT_PLAIN) <b>(1)</b>
    String currentDatePrefix(@Selector String prefix) {
        return "${prefix}: ${currentDate}"
    }

}

CurrentDateEndpoint

import io.micronaut.management.endpoint.annotation.Endpoint

import io.micronaut.management.endpoint.annotation.Read

import io.micronaut.http.MediaType
import io.micronaut.management.endpoint.annotation.Selector


@Endpoint(id = "date", prefix = "custom", defaultEnabled = true, defaultSensitive = false)
class CurrentDateEndpoint {

    private var currentDate: Date? = null

    @Read(produces = [MediaType.TEXT_PLAIN]) <b>(1)</b>
    fun currentDatePrefix(@Selector prefix: String): String {
        return "$prefix: $currentDate"
    }

}
1 Supported media types are represented byMediaType

The above method responds to the following request:

$ curl -X GET localhost:8080/date/the_date_is

the_date_is: Fri May 11 19:24:21 CDT

Write Methods

Annotating a method with theWrite annotation will cause it to respond to POST requests.

CurrentDateEndpoint

import io.micronaut.management.endpoint.annotation.Endpoint;

import io.micronaut.management.endpoint.annotation.Write;

import io.micronaut.http.MediaType;
import io.micronaut.management.endpoint.annotation.Selector;

@Endpoint(id = "date",
        prefix = "custom",
        defaultEnabled = true,
        defaultSensitive = false)
public class CurrentDateEndpoint {

    private Date currentDate;

    @Write
    public String reset() {
        currentDate = new Date();

        return "Current date reset";
    }

}

CurrentDateEndpoint

import io.micronaut.management.endpoint.annotation.Endpoint

import io.micronaut.management.endpoint.annotation.Write

import io.micronaut.http.MediaType
import io.micronaut.management.endpoint.annotation.Selector


@Endpoint(id = "date",
        prefix = "custom",
        defaultEnabled = true,
        defaultSensitive = false)
class CurrentDateEndpoint {

    Date currentDate

    @Write
    String reset() {
        currentDate = new Date()

        return "Current date reset"
    }

}

CurrentDateEndpoint

import io.micronaut.management.endpoint.annotation.Endpoint

import io.micronaut.management.endpoint.annotation.Write

import io.micronaut.http.MediaType
import io.micronaut.management.endpoint.annotation.Selector


@Endpoint(id = "date", prefix = "custom", defaultEnabled = true, defaultSensitive = false)
class CurrentDateEndpoint {

    private var currentDate: Date? = null

    @Write
    fun reset(): String {
        currentDate = Date()

        return "Current date reset"
    }

}

The above method responds to the following request:

$ curl -X POST http://localhost:39357/date

Current date reset

TheWrite annotation accepts an optional consumes argument, which sets the media type accepted by the method (default is application/json ):

MessageEndpoint

import io.micronaut.context.annotation.Requires;
import io.micronaut.management.endpoint.annotation.Endpoint;

import io.micronaut.management.endpoint.annotation.Write;

import io.micronaut.http.MediaType;

@Endpoint(id = "message", defaultSensitive = false)
public class MessageEndpoint {

    String message;

    @Write(consumes = MediaType.APPLICATION_FORM_URLENCODED, produces = MediaType.TEXT_PLAIN)
    public String updateMessage(String newMessage) {
        this.message = newMessage;

        return "Message updated";
    }

}

MessageEndpoint

import io.micronaut.management.endpoint.annotation.Endpoint

import io.micronaut.management.endpoint.annotation.Write

import io.micronaut.http.MediaType

@Endpoint(id = "message", defaultSensitive = false)
class MessageEndpoint {

    String message

    @Write(consumes = MediaType.APPLICATION_FORM_URLENCODED, produces = MediaType.TEXT_PLAIN)
    String updateMessage(String newMessage) {  <b>(1)</b>
        message = newMessage

        return "Message updated"
    }

}

MessageEndpoint

import io.micronaut.context.annotation.Requires
import io.micronaut.management.endpoint.annotation.Endpoint

import io.micronaut.management.endpoint.annotation.Write

import io.micronaut.http.MediaType

@Endpoint(id = "message", defaultSensitive = false)
class MessageEndpoint {

    internal var message: String? = null

    @Write(consumes = [MediaType.APPLICATION_FORM_URLENCODED], produces = [MediaType.TEXT_PLAIN])
    fun updateMessage(newMessage: String): String {  <b>(1)</b>
        this.message = newMessage

        return "Message updated"
    }

}

The above method responds to the following request:

$ curl -X POST http://localhost:65013/message -H 'Content-Type: application/x-www-form-urlencoded' -d $'newMessage=A new message'

Message updated

Delete Methods

Annotating a method with theDelete annotation will cause it to respond to DELETE requests.

MessageEndpoint

import io.micronaut.context.annotation.Requires;
import io.micronaut.management.endpoint.annotation.Endpoint;

import io.micronaut.management.endpoint.annotation.Delete;

@Endpoint(id = "message", defaultSensitive = false)
public class MessageEndpoint {

    String message;

    @Delete
    public String deleteMessage() {
        this.message = null;

        return "Message deleted";
    }

}

MessageEndpoint

import io.micronaut.management.endpoint.annotation.Endpoint

import io.micronaut.management.endpoint.annotation.Delete

@Endpoint(id = "message", defaultSensitive = false)
class MessageEndpoint {

    String message

    @Delete
    String deleteMessage() {
        message = null

        return "Message deleted"
    }

}

MessageEndpoint

import io.micronaut.context.annotation.Requires
import io.micronaut.management.endpoint.annotation.Endpoint

import io.micronaut.management.endpoint.annotation.Delete

@Endpoint(id = "message", defaultSensitive = false)
class MessageEndpoint {

    internal var message: String? = null

    @Delete
    fun deleteMessage(): String {
        this.message = null

        return "Message deleted"
    }

}

The above method responds to the following request:

$ curl -X DELETE http://localhost:65013/message

Message deleted

14.1.3 Endpoint Sensitivity

Endpoint sensitivity can be controlled for the entire endpoint through the endpoint annotation and configuration. Individual methods can be configured independently from the endpoint as a whole, however. The@Sensitive annotation can be applied to methods to control their sensitivity.

AlertsEndpoint

import io.micronaut.context.annotation.Requires;
import io.micronaut.http.MediaType;
import io.micronaut.management.endpoint.annotation.*;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

@Endpoint(id = "alerts", defaultSensitive = false) <b>(1)</b>
public class AlertsEndpoint {

    private final List<String> alerts = new CopyOnWriteArrayList<>();

    @Read
    List<String> getAlerts() {
        return alerts;
    }

    @Delete
    @Sensitive(true)  <b>(2)</b>
    void clearAlerts() {
        alerts.clear();
    }

    @Write(consumes = MediaType.TEXT_PLAIN)
    @Sensitive(property = "add.sensitive", defaultValue = true)  <b>(3)</b>
    void addAlert(String alert) {
        alerts.add(alert);
    }
}

AlertsEndpoint

import io.micronaut.context.annotation.Requires;
import io.micronaut.http.MediaType;
import io.micronaut.management.endpoint.annotation.*;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

@Endpoint(id = "alerts", defaultSensitive = false) <b>(1)</b>
class AlertsEndpoint {

    private final List<String> alerts = new CopyOnWriteArrayList<>();

    @Read
    List<String> getAlerts() {
        alerts
    }

    @Delete
    @Sensitive(true)  <b>(2)</b>
    void clearAlerts() {
        alerts.clear()
    }

    @Write(consumes = MediaType.TEXT_PLAIN)
    @Sensitive(property = "add.sensitive", defaultValue = true)  <b>(3)</b>
    void addAlert(String alert) {
        alerts.add(alert)
    }
}

AlertsEndpoint

import io.micronaut.context.annotation.Requires
import io.micronaut.http.MediaType
import io.micronaut.management.endpoint.annotation.*
import java.util.concurrent.CopyOnWriteArrayList

@Endpoint(id = "alerts", defaultSensitive = false) <b>(1)</b>
class AlertsEndpoint {

    private val alerts: MutableList<String> = CopyOnWriteArrayList()

    @Read
    fun getAlerts(): List<String> {
        return alerts
    }

    @Delete
    @Sensitive(true)  <b>(2)</b>
    fun clearAlerts() {
        alerts.clear()
    }

    @Write(consumes = [MediaType.TEXT_PLAIN])
    @Sensitive(property = "add.sensitive", defaultValue = true)  <b>(3)</b>
    fun addAlert(alert: String) {
        alerts.add(alert)
    }
}
1 The endpoint is not sensitive by default and the default prefix of endpoints is used.
2 This method is always sensitive, regardless of any other factors
3 The property value is appended to the prefix and id to lookup a configuration value

If the configuration key endpoints.alerts.add.sensitive is set, that value will determine the sensitivity of the addAlert method.

  1. endpoint is the first token because that is the default value for prefix in the endpoint annotation and is not set explicity in this example.

  2. alerts is the next token because that is the endpoint id

  3. add.sensitive is the next token because that is the value set to the property member of the@Sensitive annotation.

If the configuration key is not set, the defaultValue will be used (defaults to true ).

14.1.4 Endpoint Configuration

Endpoints with the endpoints prefix can be configured through their default endpoint id. If an endpoint exists with the id of foo , it can be configured through endpoints.foo . In addition, default values can be provided through the all prefix.

For example, consider the following endpoint.

FooEndpoint.java

@Endpoint("foo")
class FooEndpoint {
    ...
}

By default the endpoint will be enabled. To disable the endpoint, set endpoints.foo.enabled to false. If endpoints.foo.enabled is not set at all and endpoints.all.enabled is set to false, the endpoint will be disabled.

The configuration values for the endpoint override the ones for all . If endpoints.foo.enabled is explicitly set to true and endpoints.all.enabled is explicitly set to false , the endpoint will be enabled.

For all endpoints, the following configuration values can be set.

endpoints:
    <any endpoint id>:
        enabled: Boolean
        sensitive: Boolean
The base path for all endpoints is / by default. If you prefer the endpoints to be available under a different base path, configure endpoints.all.path . For example, if the value is set to /endpoints , the foo endpoint will be accessible at /endpoints/foo .

14.2 Built-In Endpoints

When the management dependency is added to your project, the following built-in endpoints are enabled by default:

Table 1. Default Endpoints
Endpoint URI Description

BeansEndpoint

/beans

Returns information about the loaded bean definitions in the application (see)

HealthEndpoint

/health

Returns information about the "health" of the application (see)

InfoEndpoint

/info

Returns static information from the state of the application (see)

LoggersEndpoint

/loggers

Returns information about available loggers and permits changing the configured log level (see)

MetricsEndpoint

/metrics

Return the. Requires the micrometer-core configuration on the classpath.

RefreshEndpoint

/refresh

Refreshes the application state (see)

RoutesEndpoint

/routes

Returns information about URIs available to be called for your application (see)

EnvironmentEndpoint

/env

Returns information about the environment and its property sources (see)

ThreadDumpEndpoint

/threaddump

Returns information about the current threads in the application.

In addition, the following built-in endpoint(s) are provided by the management dependency but are not enabled by default:

Table 2. Disabled Endpoints
Endpoint URI Description

CachesEndpoint

/caches

Returns information about the caches and permits invalidating them (see)

ServerStopEndpoint

/stop

Shuts down the application server (see)

It is possible to open all endpoints for unauthenticated access defining endpoints.all.sensitive: false but this should be used with care because private and sensitive information will be exposed.

Management Port

By default all management endpoints are exposed over the same port as the application. You can alter this behaviour by specifying the endpoints.all.port setting:

endpoints:
    all:
        port: 8085

In the above example the management endpoints will be exposed only over port 8085.

JMX

Micronaut provides functionality to register endpoints with JMX. See the section onto get started.

14.2.1 The Beans Endpoint

The beans endpoint returns information about the loaded bean definitions in the application. The bean data returned by default is an object where the key is the bean definition class name and the value is an object of properties about the bean.

To execute the beans endpoint, send a GET request to /beans.

Configuration

To configure the beans endpoint, supply configuration through endpoints.beans .

Beans Endpoint Configuration Example

endpoints:
    beans:
        enabled: Boolean
        sensitive: Boolean

Customization

The beans endpoint is composed of a bean definition data collector and a bean data implementation. The bean definition data collector ( BeanDefinitionDataCollector ) is responsible for returning a publisher that will return the data used in the response. The bean definition data (BeanDefinitionData) is responsible for returning data about an individual bean definition.

To override the default behavior for either of the helper classes, either extend the default implementations ( RxJavaBeanDefinitionDataCollector , DefaultBeanDefinitionData ), or implement the relevant interface directly. To ensure your implementation is used instead of the default, add the@Replaces annotation to your class with the value being the default implementation.

14.2.2 The Info Endpoint

The info endpoint returns static information from the state of the application. The info exposed can be provided by any number of "info sources".

To execute the info endpoint, send a GET request to /info.

Configuration

To configure the info endpoint, supply configuration through endpoints.info .

Info Endpoint Configuration Example

endpoints:
    info:
        enabled: Boolean
        sensitive: Boolean

Customization

The info endpoint consists of an info aggregator and any number of info sources. To add an info source, simply create a bean class that implementsInfoSource. If your info source needs to retrieve data from Java properties files, you can extend thePropertiesInfoSource interface which provides a helper method for this purpose.

All of the info source beans will be collected together with the info aggregator. To provide your own implementation of the info aggregator, create a class that implementsInfoAggregator and register it as a bean. To ensure your implementation is used instead of the default, add the@Replaces annotation to your class with the value being the default implementation .

The default info aggregator returns map containing the combined properties returned by all the info sources. This map is returned as JSON from the /info endpoint.

Provided Info Sources

Configuration Info Source

The ConfigurationInfoSource will return configuration properties under the info key. In addition to string, integer and boolean values, more complex properties can be exposed as maps in the JSON output (if the configuration format supports it).

Info Source Example ( application.groovy )
info.demo.string = "demo string"
info.demo.number = 123
info.demo.map = [key: 'value', other_key: 123]

The above config will result in the following JSON response from the info endpoint:

{
  "demo": {
    "string": "demo string",
    "number": 123,
    "map": {
      "key": "value",
      "other_key": 123
    }
  }
}

Configuration

The configuration info source can be disabled using the endpoints.info.config.enabled property.

Git Info Source

If a git.properties file is available on the classpath, theGitInfoSource will expose the values in that file under the git key. Generating of a git.properties file will need to be configured as part of your build. One easy option for Gradle users is the Gradle Git Properties Plugin . Maven users can look at the Maven Git Commit Id Plugin .

Configuration

To specify an alternate path/name of the properties file, you can supply a custom value in the endpoints.info.git.location property.

The git info source can be disabled using the endpoints.info.git.enabled property.

Build Info Source

If a META-INF/build-info.properties file is available on the classpath, theBuildInfoSource will expose the values in that file under the build key. Generating of a build-info.properties file will need to be configured as part of your build. One easy option for Gradle users is the Gradle Build Info Plugin . Another option for Maven users is the Spring Boot Maven Plugin

Configuration

To specify an alternate path/name of the properties file, you can supply a custom value in the endpoints.info.build.location property.

The build info source can be disabled using the endpoints.info.build.enabled property.

14.2.3 The Health Endpoint

The health endpoint returns information about the "health" of the application. The application health is determined by any number of "health indicators".

To execute the health endpoint, send a GET request to /health.

Configuration

To configure the health endpoint, supply configuration through endpoints.health .

Health Endpoint Configuration Example

endpoints:
    health:
        enabled: Boolean
        sensitive: Boolean
        details-visible: String <i data-value="1"></i><b>(1)</b>
        status:
            http-mapping: Map<String, HttpStatus>
1 One ofDetailsVisibility

The details-visible setting is used to control whether health detail should be exposed to users who are not authenticated.

For example setting:

Using details-visible
endpoints:
    health:
        details-visible: ANONYMOUS

Will expose detailed information read from the various health indicators about the health status of the application to anonymous unauthenticated users.

The endpoints.health.status.http-mapping setting can be used to control what status codes are returned for each health status. The defaults are described in the table below:

Status HTTP Code

UP

OK (200)

UNKNOWN

OK (200)

DOWN

SERVICE_UNAVAILABLE (503)

You can provide custom mappings in application.yml :

Custom Health Status Codes

endpoints:
    health:
        status:
            http-mapping:
                DOWN: 200

The above will returnOK (200) even when theHealthStatus isDOWN.

Customization

The health endpoint consists of a health aggregator and any number of health indicators. To add a health indicator, simply create a bean class that implementsHealthIndicator. A base class AbstractHealthIndicator has been provided for you to extend to make the process easier.

All of the health indicator beans will be collected together with the health aggregator. To provide your own implementation of the health aggregator, create a class that implementsHealthAggregator and register it as a bean. To ensure your implementation is used instead of the default, add the@Replaces annotation to your class with the value being the default implementation .

The default health aggregator returns an overall status that is calculated based on the health statuses of the indicators. Ahealth status consists of several pieces of information.

Name

The name of the status

Description

The description of the status

Operational

Whether the functionality the indicator represents is functional

Severity

How severe the status is. A higher number is more severe

The "worst" status will be returned as the overall status. A non operational status will be selected over an operational status. A higher severity will be selected over a lower severity.

Provided Indicators

Disk Space

A health indicator is provided that determines the health of the application based on the amount of free disk space. Configuration for the disk space health indicator can be provided under the endpoints.health.disk-space key.

Disk Space Indicator Configuration Example

endpoints:
    health:
        disk-space:
            enabled: Boolean
            path: String #The file path used to determine the disk space
            threshold: String | Long #The minimum amount of free space

The threshold can be provided as a string like "10MB" or "200KB", or the number of bytes.

JDBC

The JDBC health indicator determines the health of your application based on the ability to successfully create connections to datasources in the application context. The only configuration option supported is to enable or disable the indicator by the endpoints.health.jdbc.enabled key.

Discovery Client

If your application is using service discovery, a health indicator will be included to monitor the health of the discovery client. The data returned can include a list of the services available.

14.2.4 The Metrics Endpoint

Micronaut includes the ability to expose Metrics via integration with Micrometer .

Using the CLI

If you are creating your project using the Micronaut CLI, supply one of the micrometer features to enable metrics and preconfigure the selected registery in your project. For example:

$ mn create-app my-app --features micrometer-atlas

The metrics endpoint returns information about the "metrics" of the application. To execute the metrics endpoint, send a GET request to /metrics . This will return a list of availble metric names.

You can get specific metrics by using /metrics/[name] such as /metrics/jvm.memory.used .

See the documentation for Micronaut Micrometer for a list of registries and information on how to configure, expose and customize metrics output.

14.2.5 The Refresh Endpoint

The refresh endpoint will refresh the application state, causing allRefreshable beans in the context to be destroyed and reinstantiated upon further requests. This is accomplished by publishing aRefreshEvent in the Application Context.

To execute the refresh endpoint, send a POST request to /refresh.

$ curl -X POST http://localhost:8080/refresh

When executed without a body, the endpoint will first refresh theEnvironment and perform a diff to detect any changes, and will then only perform the refresh if changes are detected. To skip this check and refresh all @Refreshable beans regardless of environment changes (e.g., to force refresh of cached responses from third-party services), you can supply a force parameter in the POST request body.

$ curl -X POST http://localhost:8080/refresh -H 'Content-Type: application/json' -d '{"force": true}'

Configuration

To configure the refresh endpoint, supply configuration through endpoints.refresh .

Beans Endpoint Configuration Example

endpoints:
    refresh:
        enabled: Boolean
        sensitive: Boolean

14.2.6 The Routes Endpoint

The routes endpoint returns information about URIs available to be called for your application. By default the data returned includes the URI, allowed method, content types produced, and information about the method that would be executed.

To execute the routes endpoint, send a GET request to /routes.

Configuration

To configure the routes endpoint, supply configuration through endpoints.routes .

Routes Endpoint Configuration Example

endpoints:
    routes:
        enabled: Boolean
        sensitive: Boolean

Customization

The routes endpoint is composed of a route data collector and a route data implementation. The route data collector (RouteDataCollector) is responsible for returning a publisher that will return the data used in the response. The route data (RouteData) is responsible for returning data about an individual route.

To override the default behavior for either of the helper classes, either extend the default implementations ( RxJavaRouteDataCollector ,DefaultRouteData), or implement the relevant interface directly. To ensure your implementation is used instead of the default, add the@Replaces annotation to your class with the value being the default implementation.

14.2.7 The Loggers Endpoint

The loggers endpoint returns information about the available loggers in the application and permits configuring the their log level.

The loggers endpoint is disabled by default and needs to be explicitly enabled with the setting endpoints.loggers.enabled=true .

To get a collection of all loggers by name with their configured and effective log levels, send a GET request to /loggers. This will also provide a list of the available log levels.

$ curl http://localhost:8080/loggers

{
    "levels": [
        "ALL", "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF", "NOT_SPECIFIED"
    ],
    "loggers": {
        "ROOT": {
            "configuredLevel": "INFO",
            "effectiveLevel": "INFO"
        },
        "io": {
            "configuredLevel": "NOT_SPECIFIED",
            "effectiveLevel": "INFO"
        },
        "io.micronaut": {
            "configuredLevel": "NOT_SPECIFIED",
            "effectiveLevel": "INFO"
        },
        // etc...
    }
}

To get the log levels of a particular logger, include the logger name in your GET request. For example, to access the log levels of the logger 'io.micronaut.http':

$ curl http://localhost:8080/loggers/io.micronaut.http

{
    "configuredLevel": "NOT_SPECIFIED",
    "effectiveLevel": "INFO"
}

If the named logger does not exist, it will be created with an unspecified (i.e. NOT_SPECIFIED ) configured log level. (It’s effective log level will usually be that of the root logger.)

To update the log level of a single logger, send a POST request to the named logger URL and include a body providing the log level to configure.

$ curl -i -X POST
       -H "Content-Type: application/json" \
       -d '{ "configuredLevel": "ERROR" }' \
       http://localhost:8080/loggers/ROOT

HTTP/1.1 200 OK

$ curl http://localhost:8080/loggers/ROOT

{
    "configuredLevel": "ERROR",
    "effectiveLevel": "ERROR"
}

Configuration

To configure the loggers endpoint, supply configuration through endpoints.loggers .

Loggers Endpoint Configuration Example

endpoints:
    loggers:
        enabled: Boolean
        sensitive: Boolean

Customization

The loggers endpoint is composed of two customizable parts: a loggers manager and a logging system.

The loggers manager (LoggersManager) is responsible for returning a Publisher that will return data collected and transformed for the response, and it is also responsible for updating a logger with a new log level.

To override the default behavior for the loggers manager, either extend the default implementation ( DefaultLoggersManager ) or implement theLoggersManager interface directly. To ensure your implementation is used instead of the default, add the@Replaces annotation to your class with the value being the default implementation.

The logging system (LoggingSystem) is responsible for processing requests from the loggers manager against a particular logging library (e.g. logback, log4j, etc.)

The current default implementation for the logging system isLogbackLoggingSystem, which works with the logback logging framework. Additional logging systems will be implemented in future revisions of Micronaut. For custom logging system behavior, implement theLoggingSystem interface directly. To ensure your implementation is used instead of the default, add the@Replaces annotation to your class with the value being the default implementation.

@Singleton
@Replaces(LogbackLoggingSystem.class)
public class CustomLoggingSystem implements LoggingSystem {
    // ...
}

14.2.8 The Caches Endpoint

The caches endpoint returns information about the caches in the application and permits invalidating them.

To get a collection of all caches by name with their configuration, send a GET request to /caches.

$ curl http://localhost:8080/caches

To get the configuration of a particular cache, include the cache name in your GET request. For example, to access the configuration of the cache 'book-cache':

$ curl http://localhost:8080/caches/book-cache

To invalidate all cached values within a single cache, send a DELETE request to the named cache URL.

$ curl -X DELETE http://localhost:8080/caches/book-cache

To invalidate all caches, send a DELETE request to /caches.

$ curl -X DELETE http://localhost:8080/caches

Configuration

To configure the caches endpoint, supply configuration through endpoints.caches .

Caches Endpoint Configuration Example

endpoints:
    caches:
        enabled: Boolean
        sensitive: Boolean

Customization

The caches endpoint is composed of a cache data collector and a cache data implementation. The cache data collector (CacheDataCollector) is responsible for returning a publisher that will return the data used in the response. The cache data (CacheData) is responsible for returning data about an individual cache.

To override the default behavior for either of the helper classes, either extend the default implementations ( RxJavaRouteDataCollector ,DefaultRouteData), or implement the relevant interface directly. To ensure your implementation is used instead of the default, add the@Replaces annotation to your class with the value being the default implementation.

14.2.9 The Server Stop Endpoint

The stop endpoint shuts down the application server.

To execute the stop endpoint, send a POST request to /stop.

Configuration

To configure the stop endpoint, supply configuration through endpoints.stop .

Stop Endpoint Configuration Example

endpoints:
    stop:
        enabled: Boolean
        sensitive: Boolean
By default, the stop endpoint is disabled and needs to be explicitly enabled to be used.

14.2.10 The Environment Endpoint

The environment endpoint returns information about theEnvironment and itsPropertySources.

Properties that may contain sensitive data will be masked.

Configuration

To configure the environment endpoint, supply configuration through endpoints.env .

Environment Endpoint Configuration Example

endpoints:
    env:
        enabled: Boolean    # default: true
        sensitive: Boolean  # default: true

Getting information about the environment

To execute the endpoint, send a GET request to /env .

Getting information about a particular PropertySource

To execute the endpoint, send a GET request to /env/{propertySourceName} .

15 Security

Micronaut has a full featured security solution for all of the common security patterns.

See the documentation for Micronaut Security for more information on how to secure your applications.

16 Multi-Tenancy

Multi-Tenancy, as it relates to software development, is when a single instance of an application is used to service multiple clients (tenants) in a way that each tenant’s data is isolated from the other.

To use the Micronaut’s multitenancy capabilities you must have the multitenancy dependency on your classpath.

implementation("io.micronaut:micronaut-multitenancy")
<dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-multitenancy</artifactId>
</dependency>

A common requirement for supporting Multi-tenancy is the ability to resolve the current tenant. Micronaut ships with the following built-inTenantResolvers:

name

description

CookieTenantResolver

Resolves the current tenant from an HTTP cookie. See CookieTenantResolver Configuration Properties .

FixedTenantResolver

Resolves against a fixed tenant id. See FixTenantResolver Configuration Properties .

HttpHeaderTenantResolver

Resolves the current tenant from the request HTTP Header. See FixTenantResolver Configuration Properties .

PrincipalTenantResolver

Resolves the current tenant from the authenticated username. See PrincipalTenantResolver Configuration Properties .

SessionTenantResolver

Resolves the current tenant from the HTTP session. See SessionTenantResolver Configuration Properties .

SubdomainTenantResolver

Resolves the tenant id from the subdomain. See SubdomainTenantResolver Configuration Properties .

SystemPropertyTenantResolver

Resolves the tenant id from a system property. See SystemPropertyTenantResolver Configuration Properties .

Micronaut supports tenant propagation. As an example, take the following scenario:

Micronaut 2.0: a full stack Java framework for modular, testable applications

You want incoming requests to the gateway microservice to resolve the tenant id via subdomain. However, you want your requests to other internal microservices to include the tenant Id as an HTTP Header.

Your configuration in the gateway microservice will look like:

micronaut:
  multitenancy:
    propagation:
      enabled: true
      service-id-regex: 'catalogue'
    tenantresolver:
      subdomain:
        enabled: true
    tenantwriter:
      httpheader:
        enabled: true

In the catalogue microservice the configuration will look like:

micronaut:
  multitenancy:
    tenantresolver:
      httpheader:
        enabled: true

To propagate the tenant you will need to write the resolved tenant ID to the outgoing requests.

Currently, Micronaut ships with two built-in implementations forTenantWriter:

name

description

CookieTenantWriter

Writes the current tenant to a Cookie in your outgoing requests. See CookieTenantWriter Configuration Properties .

HttpHeaderTenantWriter

Writes the current tenant to a HTTP Header. See HttpHeaderTenantWriter Configuration Properties .

16.1 Multi-Tenancy GORM

GORM supports Multi-tenancy and integrates with Micronaut.

To use Micronaut and GORM multitenancy capabilities you must have the multitenancy-gorm dependency on your classpath. For example in build.gradle :

build.gradle

compile "io.micronaut.configuration:micronaut-multitenancy-gorm"

GORM is a powerful Groovy-based data access toolkit for the JVM with implementations several data access technologies (Hibernate, Neo4j, MongoDB, GraphQL …​).

GORM supports the following different multitenancy modes:

  • DATABASE - A separate database with a separate connection pool is used to store each tenants data.

  • SCHEMA - The same database, but different schemas are used to store each tenants data.

  • DISCRIMINATOR - The same database is used with a discriminator used to partition and isolate data.

In order to use GORM - Multitenancy you will need to configure the following properties: grails.gorm.multiTenancy.mode and grails.gorm.multiTenancy.tenantResolverClass .

Micronaut support for Multi-tenancy integrates with GORM.

The following table contains all of the TenantResolver implementations that ship with `multitenancy-gorm module and are usable out of the box.

name

description

CookieTenantResolver

Resolves the current tenant from an HTTP cookie.

FixedTenantResolver

Resolves against a fixed tenant id

HttpHeaderTenantResolver

Resolves the current tenant from the request HTTP Header.

PrincipalTenantResolver

Resolves the current tenant from the authenticated username.

SessionTenantResolver

Resolves the current tenant from the HTTP session.

SubdomainTenantResolver

Resolves the tenant id from the subdomain.

SystemPropertyTenantResolver

Resolves the tenant id from a system property.

You will need to add something like the following snippet to your app configuration:

grails:
    gorm:
        multiTenancy:
            mode: DISCRIMINATOR
            tenantResolverClass: 'io.micronaut.multitenancy.gorm.PrincipalTenantResolver'

Please, read GORM Multi-tenancy documentation to learn more.

17 Micronaut CLI

The Micronaut CLI is the recommended way to create new Micronaut projects. The CLI includes commands for generating specific categories of projects, allowing you to choose between build tools, test frameworks, and even pick the language you wish to use in your application. The CLI also provides commands for generating artifacts such as controllers, client interfaces, and serverless functions.

We have a website that can be used to generate projects instead of the CLI. Check outMicronaut Launch to get started!

WhenMicronaut is installed on your computer, you can call the CLI with the mn command.

$ mn create-app my-app

A Micronaut CLI project can be identified by the micronaut-cli.yml file, which will be included at the root of the project (if it was generated via the CLI). This file will include the project’s profile, default package, and other variables. The project’s default package is evaluated based on the project name.

$ mn create-app my-demo-app

Will result in the default package being my.demo.app .

You can supply your own default package when creating the application by prefixing the application name with the package:

$ mn create-app example.my-demo-app

Will result in the default package being example .

Interactive Mode

If you run mn without any arguments, the Micronaut CLI will launch in interactive mode. This is a shell-like mode which allows you to run multiple CLI commands without re-initializing the CLI runtime, and is especially suitable when you are making use of code-generation commands (such as create-controller ), creating multiple projects, or simply exploring the features included in the CLI. Tab-completion is enabled in the CLI, enabling you to hit the TAB key to see possible options for a given command or flag.

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

Help and Info

General usage information can be viewed using the help flag on a command.

mn> create-app -h
Usage: mn create-app [-hivVx] [--list-features] [-b=BUILD-TOOL] [--jdk=<javaVersion>] [-l=LANG]
                     [-t=TEST] [-f=FEATURE[,FEATURE...]]... [NAME]
Creates an application
      [NAME]               The name of the application to create.
  -b, --build=BUILD-TOOL   Which build tool to configure. Possible values: gradle, maven.
  -f, --features=FEATURE[,FEATURE...]
  -h, --help               Show this help message and exit.
  -i, --inplace            Create a service using the current directory
      --jdk, --java-version=<javaVersion>
                           The JDK version the project should target
  -l, --lang=LANG          Which language to use. Possible values: java, groovy, kotlin.
      --list-features      Output the available features and their descriptions
  -t, --test=TEST          Which test framework to use. Possible values: junit, spock, kotlintest.

A list of available features can be viewed using the --list-features flag on any of the create commands.

mn> create-app --list-features
Available Features
(+) denotes the feature is included by default
  Name                             Description
  -------------------------------  ---------------
  Cache
  cache-caffeine                   Adds support for cache using Caffeine (https://github.com/ben-manes/caffeine)
  cache-ehcache                    Adds support for cache using EHCache (https://www.ehcache.org/)
  cache-hazelcast                  Adds support for cache using Hazelcast (https://hazelcast.org/)
  cache-infinispan                 Adds support for cache using Infinispan (https://infinispan.org/)

17.1 Creating a Project

Creating a project is the primary usage of the CLI. There primary command for creating a new project is create-app . That will create a standard server application that communicates over HTTP. For other types of applications, see the documentation below.

Table 1. Micronaut CLI Project Creation Commands
Command Description Options Example

create-app

Creates a basic Micronaut application.

  • -l , --lang

  • -t , --test

  • -b , --build

  • -f , --features

  • -i , --inplace

mn create-app my-project --features
 mongo-reactive,security-jwt --build maven

create-cli-app

Creates a command-line Micronaut application.

  • -l , --lang

  • -t , --test

  • -b , --build

  • -f , --features

  • -i , --inplace

mn create-cli-app my-project --features
 http-client,jdbc-hikari --build maven
 --lang kotlin --test kotlintest

create-function-app

Creates a Micronaut serverless function, using AWS by default.

  • -l , --lang

  • -t , --test

  • -b , --build

  • -f , --features

  • -i , --inplace

mn create-function-app my-lambda-function
 --lang groovy --test spock

create-messaging-app

Creates a Micronaut application that only communicates via a messaging protocol. Uses Kafka by default but can be switched to RabbitMQ with --features rabbitmq .

  • -l , --lang

  • -t , --test

  • -b , --build

  • -f , --features

  • -i , --inplace

mn create-messaging-app my-broker
 --lang groovy --test spock

create-grpc-app

Creates a Micronaut application that uses gRPC.

  • -l , --lang

  • -t , --test

  • -b , --build

  • -f , --features

  • -i , --inplace

mn create-grpc-app my-grpc-app
 --lang groovy --test spock

Create Command Flags

The create-* commands will generate a basic Micronaut project, with optional flags to specify features, language, test framework, and build tool. All projects except functions will include a default Application class for starting the application.

Table 2. Flags
Flag Description Example

-l , --lang

Language to use for the project (one of java , groovy , kotlin - default is java )

--lang groovy

-t , --test

Test framework to use for the project (one of junit , spock - default is junit )

--test spock

-b , --build

Build tool (one of gradle , maven - default is gradle )

--build maven

-f , --features

Features to use for the project, comma-separated

--features security-jwt,mongo-gorm

or

-f security-jwt -f mongo-gorm

-i , --inplace

If present, generates the project in the current directory (project name is optional if this flag is set)

--inplace

Once created, the application can be started using the Application class, or the appropriate build tool task.

Starting a Gradle project

$ ./gradlew run

Starting a Maven project

$ ./mvnw mn:run

Language/Test Features

By default, the create commands will generate a Java application, with JUnit configured as the test framework. All of the options chosen and features applied are stored as properties in the micronaut-cli.yml file, as shown below:

micronaut-cli.yml

applicationType: default
defaultPackage: com.example
testFramework: junit
sourceLanguage: java
buildTool: gradle
features: [annotation-api, app-name, application, gradle, http-client, java, junit, logback, netty-server, shade, yaml]

Some commands rely on the data in this file in order to determine if they should be executable. For example, the create-kafka-listener command requires kafka to be one of the features in the list.

The values in micronaut-cli.yml are used by the CLI for code generation purposes. After a project has been generated, you can edit these values to change the project defaults, however you will still need to supply the required dependencies and/or configuration in order to use your chosen language/framework. E.g, you could edit the testFramework property to spock to cause the CLI to generate Spock tests when running commands (such as create-controller ), but you will still need to add the Spock dependency to your project.

Groovy

To create an app with Groovy support (which comes with Spock by default), supply the appropriate language via the lang flag:

$ mn create-app my-groovy-app --lang groovy

This will include the Groovy & Spock dependencies in your project, and write the appropriates values in micronaut-cli.yml .

Kotlin

To create an app with Kotlin support (which comes with Kotlintest by default), supply the appropriate language via the lang flag:

$ mn create-app my-kotlin-app --lang kotlin

This will include the Kotlin & Kotlintest dependencies in your project, and write the appropriates values in micronaut-cli.yml .

Build Tool

By default create-app will create a Gradle project, with a build.gradle file at the root of the project directory. To create an app using the Maven build tool, supply the appropriate option via the build flag:

$ mn create-app my-maven-app --build maven

Create-Cli-App

The create-cli-app command will generate aMicronaut command line applicationproject, with optional flags to specify language, test framework, features, profile, and build tool. By default the project will have the picocli feature to support command line option parsing. The project will include a *Command class (based on the project name - e.g., hello-world will generate HelloWorldCommand ), and an associated test which will instantiate the command and verify that it can parse command line options.

Once created, the application can be started using the *Command class, or the appropriate build tool task.

Starting a Gradle project

$ ./gradlew run

Starting a Maven project

$ ./mvnw mn:run

Create Function App

The create-function-app command will generate aproject, optimized for serverless environments, with optional flags to specify language, test framework, features and build tool. The project will include a *Function class (based on the project name - e.g., hello-world will generate HelloWorldFunction ), and an associated test which will instantiate the function and verify that it can receive requests.

Currently AWS Lambda, Micronaut Azure, and Google Cloud are the supported cloud providers for Micronaut functions. To use other providers, add one in the features: --features azure-function or --features google-cloud-function .

Contribute

The CLI’s source code is at https://github.com/micronaut-projects/micronaut-starter . Information about how to contribute and other resources are there.

17.2 Profiles

Profiles are project templates, consisting of a "skeleton" project structure with default configuration, build dependencies and other assets, along with a set of commands for generating application-appropriate artifacts (such as controllers, client interfaces, etc).

Profiles can inherit from other profiles, augmenting/overriding defaults set in their parent. All profiles inherit from the base profile, which provides the bare minimum required to set up a Micronaut application.

Many Micronaut apps will use the service profile, which augments the base profile to add HTTP client/server libraries, create-controller and create-client commands, and other features that are appropriate for web service applications. This is the default profile used when running the create-app command.

Other Micronaut apps will be better served by the cli profile, which adds the create-command command and the picocli library for parsing command line input. This is the default profile used when running the create-cli-app command.

You can view all of the built-in profiles using the CLI command list-profiles .

Profiles can be chosen using the profile flag on the create-app , create-cli-app , and create-federation commands.

$ mn create-app my-project --profile rabbitmq

17.2.1 Comparing Versions

The easiest way to see version dependency updates and other changes for a new version of Micronaut, is to produce one clean application using the older and one using the newer version of the mn CLI, and then comparing those directories.

17.3 Features

Features consist of additional dependencies and configuration to enable specific functionality in your application. The Micronaut profiles define a large number of features, including features for many of the configurations provided by Micronaut, such as theData Access Configurations

$ mn create-app my-demo-app --features mongo-reactive

This will add the necessary dependencies and configuration for the MongoDB Reactive Driver in your application. You can view all the available features using the profile-info command.

$ mn profile-info service

17.4 Commands

Profiles can include commands for generating common code artifacts. Not all commands are supported by all profiles; the available commands for a given profile can be viewed using the profile-info command.

$ mn profile-info service
Profile: service
------------------
The service profile

Provided Commands:
--------------------
  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
  create-test              Creates a simple test for the project's testing framework
  create-websocket-client  Creates a Websocket client
  create-websocket-server  Creates a Websocket server
  help                     Prints help information for a specific command


Provided Features:
------------------
...

All of the code-generation commands will honor the values written in micronaut-cli.yml . For example, assume the following micronaut-cli.yml file.

micronaut-cli.yml

profile: service
defaultPackage: example
---
testFramework: spock
sourceLanguage: java

With the above settings, the create-controller command will (by default) generate Java controllers with an associated Spock test, under the example package. Commands accept arguments and these defaults can be overriden on a per-command basis.

Base Profile Commands

Commands in the base profile are inherited by all other profiles and are always available.

Create-Bean

Table 1. Create-Bean Flags
Flag Description Example

-l , --lang

The language used for the bean class

--lang groovy

-f , --force

Whether to overwrite existing files

--force

The create-bean command generates a simple Singleton class. It does not create an associated test.

$ mn create-bean EmailService
| Rendered template Bean.java to destination src/main/java/example/EmailService.java

Create-Job

Table 2. Create-Job Flags
Flag Description Example

-l , --lang

The language used for the job class

--lang groovy

-f , --force

Whether to overwrite existing files

--force

The create-job command generates a simpleScheduled class. It follows a *Job convention for generating the class name. It does not create an associated test.

$ mn create-job UpdateFeeds --lang groovy
| Rendered template Job.groovy to destination src/main/groovy/example/UpdateFeedsJob.groovy

Service Profile Commands

The service profile includes commands that are dependent upon the HTTP server and client dependencies (which are not included in the base profile).

Create-Controller

Table 3. Create-Controller Flags
Flag Description Example

-l , --lang

The language used for the controller

--lang groovy

-f , --force

Whether to overwrite existing files

--force

The create-controller command generates aController class. It follows a *Controller convention for generating the class name. It creates an associated test that will run the application and instantiate an HTTP client, which can make requests against the controller.

$ mn create-controller Book
| Rendered template Controller.java to destination src/main/java/example/BookController.java
| Rendered template ControllerTest.java to destination src/test/java/example/BookControllerTest.java

Create-Client

Table 4. Create-Client Flags
Flag Description Example

-l , --lang

The language used for the client

--lang groovy

-f , --force

Whether to overwrite existing files

--force

The create-client command generates a simpleClient interface. It follows a *Client convention for generating the class name. It does not create an associated test.

$ mn create-client Book
| Rendered template Client.java to destination src/main/java/example/BookClient.java

Create-Websocket-Server

Table 5. Create-Websocket-Server Flags
Flag Description Example

-l , --lang

The language used for the server

--lang groovy

-f , --force

Whether to overwrite existing files

--force

The create-websocket-server command generates a simpleWebSocketServer class. It follows a *Server convention for generating the class name. It does not create an associated test.

$ mn create-websocket-server MyChat
| Rendered template WebsocketServer.java to destination src/main/java/example/MyChatServer.java

Create-Websocket-Client

Table 6. Create-Websocket-Client Flags
Flag Description Example

-l , --lang

The language used for the client

--lang groovy

-f , --force

Whether to overwrite existing files

--force

The create-websocket-client command generates a simpleWebSocketClient abstract class. It follows a *Client convention for generating the class name. It does not create an associated test.

$ mn create-websocket-client MyChat
| Rendered template WebsocketClient.java to destination src/main/java/example/MyChatClient.java

CLI Profile Commands

The cli profile lets you generate CLI commands to control your application.

Create-Command

Table 7. Create-Command Flags
Flag Description Example

-l , --lang

The language used for the command

--lang groovy

-f , --force

Whether to overwrite existing files

--force

The create-command command generates a standalone application that can be executed as a picocli Command . It follows a *Command convention for generating the class name. It creates an associated test that will run the application and verify that a command line option was set.

$ mn create-command print
| Rendered template Command.java to destination src/main/java/example/PrintCommand.java
| Rendered template CommandTest.java to destination src/test/java/example/PrintCommandTest.java

17.5 Reloading

Reloading (or "hot-loading") refers to the framework reinitializing classes (and parts of the application) when changes to the source files are detected.

Since Micronaut prioritizes startup time and most Micronaut apps can start up within seconds, a productive workflow can often be had by restarting the application as changes are made; for example, by running a test class within an IDE.

However, if you wish to have your changes automatically reloaded Micronaut does support automatic restart or the use of third-party reloading agents.

17.5.1 Automatic Restart

There are various ways to achieve reloading of classes on the JVM, all have their advantages and disadvantages. The following are possible ways to achieve reloading without restarting the JVM:

  • JVM Agents- A JVM agent like JRebel can be used, however these can produce unusual errors, may not support all JDK versions and can result in cached / stale classes.

  • ClassLoader Reloading- Class Loader based reloading is a popular solution used by most JVM frameworks, however it once again can lead to cached / stale classes, memory leaks and weird errors if the incorrect classloader is used at any moment.

  • Debugger HotSwap- The Java debugger supports hotswapping of changes at runtime, but only supports a few use cases.

Given the problems with existing solutions and a lack of a way built into the JVM to reload changes, the safest and best solution to reloading, and the one recommended by the Micronaut team, is to use automatic application restart via third-party tool.

Micronaut’s startup time is fast and automatic restart leads to a clean slate without potential hard to debug problems cropping up or memory leaks.

Maven Restart

To have automatic application restarts with Maven use the Micronaut Maven plugin (including by default when creating new Maven projects) and run the following command:

Using the Micronaut Maven Plugin

$ ./mvnw mn:run

Every time you change a class Micronaut’s plugin will automatically restart the server.

Gradle Restart

For Gradle restart you can create a new application with the file-watch feature already configured using the CLI with the command mn create-app myapp --features file-watch

This essentially introduces the following configuration to application.yml :

File Watch Configuration

micronaut:
    io:
        watch:
            paths: src/main
            restart: true

By setting micronaut.io.watch.paths what happens is that Micronaut will fire aFileChangedEvent if any file is changed. Setting micronaut.io.watch.restart tells Micronaut to stop the server if a file is changed.

The micronaut.io.watch.paths setting can be used for more than just automatic restart, you can use this setting to monitor for file changes within your production application.

Once this is in place to have Gradle automatically restart on any change add run the following command:

Using Gradle for Automatic Restart

./gradlew run --continuous

Every time you make a change to class or resources Gradle will recompile and restart the application.

File Watch and Mac OS X

The native JVM implementation of the WatchService interface for Mac OS X using polling which is slow. To improve file watch performance add the following dependencies to your build if you are using OS X:

Gradle - Configuring Native File Watch on Mac OS X

configurations {
    developmentOnly
}
dependencies {
    // Automatically added with the 'file-watch' feature in the CLi
    developmentOnly "io.micronaut:micronaut-runtime-osx"
    ...
    developmentOnly "net.java.dev.jna:jna"
    developmentOnly "io.methvin:directory-watcher"

}
run.classpath += configurations.developmentOnly
A custom developmentOnly configuration is used to ensure the dependencies don’t go into the production JAR.

Or with Maven:

<dependency>
  <groupId>io.methvin</groupId>
  <artifactId>directory-watcher</artifactId>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>net.java.dev.jna</groupId>
  <artifactId>jna</artifactId>
  <scope>provided</scope>
</dependency>
The provided scope is used to ensure the dependencies don’t go into the production JAR.

17.5.2 JRebel

JRebel is a proprietary reloading solution that involves an agent library, as well as sophisticated IDE support. The JRebel documentation includes detailed steps for IDE integration and usage of the tool. In this section, we will simply show how to install and configure the agent for Maven and Gradle projects.

Using the CLI

If you are creating your project using the Micronaut CLI, supply the jrebel feature to preconfigure JRebel reloading in your project. Please note that you will still need to install JRebel and supply the correct path to the agent in the gradle.properties file (for Gradle) or pom.xml (for Maven). The steps necessary are described below.

$ mn create-app my-app --features jrebel

Install/configure JRebel Agent

The simplest way to install JRebel is to download the "standalone" installation package from the JRebel download page . Unzip the downloaded file to a convenient location within your user’s directory - for example, ~/bin/jrebel

The installation directory will contain a lib directory containing the agent files. The appropriate agent based on your operating system, following the table below:

Table 1. JRebel Agent
OS Agent

Windows 64-bit JDK

[jrebel directory]\lib\jrebel64.dll

Windows 32-bit JDK

[jrebel directory]\lib\jrebel32.dll

Mac OS X 64-bit JDK

[jrebel directory]/lib/libjrebel64.dylib

Mac OS X 32-bit JDK

[jrebel directory]/lib/libjrebel32.dylib

Linux 64-bit JDK

[jrebel directory]/lib/libjrebel64.so

Linux 32-bit JDK

[jrebel directory]/lib/libjrebel32.so

Note the path to the appropriate agent, and add the value to your project build.

Gradle

Add the path to gradle.properties (create the file if necessary), as the rebelAgent property.

gradle.properties

#Assuming installation path of ~/bin/jrebel/
rebelAgent= -agentpath:~/bin/jrebel/lib/libjrebel64.dylib

Add the appropriate JVM arg to build.gradle (not necessary if using the CLI feature)

run.dependsOn(generateRebel)
if (project.hasProperty('rebelAgent')) {
    run.jvmArgs += rebelAgent
}

You can start the application with ./gradlew run , and it will include the agent. See the section onorto set up the recompilation.

Maven

Add the path to pom.xml as a build profile (if using the CLI feature, the profile jrebel is already provided - simply update the path to the agent JAR).

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<!-- ... -->
    <profiles>
        <profile>
            <id>jrebel</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.codehaus.mojo</groupId>
                        <artifactId>exec-maven-plugin</artifactId>
                        <version>1.6.0</version>
                        <configuration>
                            <executable>java</executable>
                            <arguments>
                                <argument>-noverify</argument>
                                <argument>-XX:TieredStopAtLevel=1</argument>
                                <argument>-agentpath:~/bin/jrebel/lib/jrebel6/lib/libjrebel64.dylib</argument>
                                <argument>-classpath</argument>
                                <classpath/>
                                <argument>${exec.mainClass}</argument>
                            </arguments>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>
</project>

17.5.3 Recompiling with Gradle

Gradle supports continuous builds , allowing you to run a task that will be rerun every time the source files change. In order to make use of this with a reloading agent (configured as described above), you will run the application normally (with the agent), and then run a recompilation task in a separate terminal with continuous mode enabled.

Run the app

$ ./gradlew run

Run the recompilation

$ ./gradlew -t classes

The classes task will be rerun every time a source file is modified, allowing the reloading agent to pick up the change.

17.5.4 Recompiling with an IDE

If you are using a build tool such as Maven, which does not support automatic recompilation on file changes, you may choose to use your IDE to recompile classes in combination with a reloading agent (as configured in the above sections).

IntelliJ

IntelliJ unfortunately does not have an automatic rebuild option that works for a running application. However, you can trigger a "rebuild" of the project with CMD-F9 (Mac) or CTRL-F9 (Windows).

Eclipse

Under the Project menu, check the Build Automatically option. This will trigger a recompilation of the project whenever file changes are saved to disk.

17.6 Proxy Configuration

To configure the CLI to use an HTTP proxy there are two steps. Configuration options can be passed to the cli through the MN_OPTS environment variable.

For example on *nix systems:

export MN_OPTS="-Dhttps.proxyHost=127.0.0.1 -Dhttps.proxyPort=3128 -Dhttp.proxyUser=test -Dhttp.proxyPassword=test"

The profile dependencies are resolved over HTTPS so the proxy port and host are configured with https. , however the user and password are specified with http. .

For Windows systems the environment variable can be configured under My Computer/Advanced/Environment Variables .

18 Appendices

18.1 Frequently Asked Questions (FAQ)

The following section covers frequently asked questions that you may find yourself asking while considering to use or using Micronaut.

Does Micronaut modify my byte code?

No. Your classes are your classes. Micronaut does not transform classes or modify the Java byte code you write. Micronaut at compile will produce an additional set of classes that sit next to your original untouched classes.

Why Doesn’t Micronaut use Spring?

When asking why Micronaut doesn’t use Spring, it is typically in reference to the Spring Dependency Injection container.

The Spring ecosystem is very broad and there are many Spring libraries you can use directly in Micronaut without requiring the Spring container.

The reason Micronaut features its own native JSR-330 compliant dependency injection is that the cost of these features in Spring (and any reflection based DI/AOP container) is too great in terms of memory consumption and the impact on startup time. In order to support dependency injection at runtime Spring:

The result is a progressive degradation of startup time and memory consumption as your application incorporates more features.

For Microservices and Serverless functions where it is critical that startup time and memory consumption remain low the above behaviour is an undesirable reality of using the Spring container, hence the designers of Micronaut chose not to use Spring.

Does Micronaut support Scala?

Micronaut supports any JVM language that supports the Annotation Processor API. Scala currently does not support this API. However, Groovy also doesn’t support this API and special support has been built that processes the Groovy AST. It may be technically possible to support Scala in the future if a module similar to inject-groovy is built, but as of this writing Scala is not supported.

Can Micronaut be used for purposes other than Microservices?

Yes. Micronaut is very modular and you can choose to use just the Dependency Injection and AOP implementation if you want simply by including only the micronaut-inject-java (or micronaut-inject-groovy for Groovy) dependency in your application.

In fact Micronaut’s support foruses this exact approach.

What are the advantages of Micronaut’s Dependency Injection and AOP implementation?

Micronaut processes your classes at compile time and produces all metadata at compile time. This eliminates the need for reflection, cached reflective metadata and the requirement to analyze your classes at runtime all of which leads to slower startup performance and greater memory consumption.

In addition, Micronaut will build AOP proxies at compile time that are completely reflection free, which improves performances, reduces stack trace sizes and reduces memory consumption.

Why does Micronaut have its own Consul and Eureka client implementations?

The majority of Consul and Eureka clients that exist are blocking and include a mountain of external dependencies that inflate your JAR files.

Micronaut’sDiscoveryClient uses Micronaut’s native HTTP client thus greatly reducing the need for external dependencies and providing a reactive API onto both discovery servers.

Why am I encountering a NoSuchMethodError occurs loading my beans (Groovy)?

Groovy by default imports the groovy.lang package which includes a class called @Singleton that is an AST transformation that makes your class a singleton (adding a private constructor and static retrieval method). This annotation is easily confused with the javax.inject.Singleton annotation used to define singleton beans in Micronaut. Make sure you are using the correct annotation in your Groovy classes.

Why is it taking much longer than it should to start the application

Micronaut’s startup time is typically very fast. At the application level, however, it is possible to block startup time so if you are seeing slow startup, review if you have any application startup listeners or @Context scope beans that are slowing startup.

Some network issues can, also cause slow startup. On the Mac for example misconfiguration of your /etc/hosts file can cause issues. See the following stackoverflow answer .

18.2 Using Snapshots

Micronaut milestone and stable releases are distributed to Bintray .

The following snippet shows how to use Micronaut BUILD-SNAPSHOT with Gradle. The latest snapshot will always be the next patch version plus 1 with .BUILD-SNAPSHOT appended. For example if the latest release is "1.0.1", the current snapshot would be "1.0.2.BUILD-SNAPSHOT".

ext {
  micronautVersion = '1.0.2.BUILD-SNAPSHOT'
}
repositories {
  jcenter() <i data-value="1"></i><b>(1)</b>
  maven { url "https://oss.jfrog.org/oss-snapshot-local" } <i data-value="2"></i><b>(2)</b>
}
dependencyManagement {
  imports {
      mavenBom "io.micronaut:micronaut-bom:$micronautVersion"
  }
}
1 Micronaut releases are available on JCenter and Maven Central
2 Micronaut snapshots are available on JFrog OSS

In the case of Maven, edit pom.xml :

<?xml version="1.0" encoding="UTF-8"?>
  ...

  <properties>
    <micronaut.version>1.0.2.BUILD-SNAPSHOT</micronaut.version> <i data-value="1"></i><b>(1)</b>
    ...
  </properties>

  <repositories>
    <repository>
      <id>jcenter.bintray.com</id>
      <url>https://jcenter.bintray.com</url> <i data-value="2"></i><b>(2)</b>
    </repository>
    <repository>
      <id>jfrog-snapshots</id>
      <url>https://oss.jfrog.org/oss-snapshot-local</url> <i data-value="3"></i><b>(3)</b>
    </repository>
  </repositories>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>io.micronaut</groupId>
        <artifactId>micronaut-bom</artifactId>
        <version>${micronaut.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  ...
</project>
1 Set the snapshot version.
2 Micronaut releases are available on JCenter and Maven Central
3 Micronaut snapshots are available on JFrog OSS Snapshots

18.3 Common Problems

The following section covers common problems folks stumble upon when using Micronaut.

Dependency injection is not working

The most common reason that Dependency Injection fails to work is when you either don’t have the appropriate annotation processor configured or your IDE is incorrectly configured. See the section onfor how to get setup in your language.

A NoSuchMethodError occurs loading beans (Groovy)

Groovy by default imports the groovy.lang package which includes a class called @Singleton that is an AST transformation that makes your class a singleton (adding a private constructor and static retrieval method). This annotation is easily confused with the javax.inject.Singleton annotation used to define singleton beans in Micronaut. Make sure you are using the correct annotation in your Groovy classes.

It is taking much longer to start my application than it should (*nix OS)

This is likely due to a bug that has to do with executions of java.net.InetAddress.getLocalHost() causing a long delay. The solution is to edit your /etc/hosts file to add an entry that contains your host name. To find your host name, simply enter hostname in the terminal and the value will be output. Then edit your /etc/hosts file to add or change entries like the example below, replacing <hostname> with your host name.

127.0.0.1       localhost <hostname>
::1             localhost <hostname>

To learn more about this issue, see this stackoverflow answer

18.4 Breaking Changes

This section will document breaking changes between Micronaut 1.0 and Micronaut 2.0

Thread Pool Selection Now Manual

In Micronaut 1.x, Micronaut would attempt to automatically deduce the thread pool to use based on the return type of the controller method.

This approach caused some confusion and created a false association between reactive types and blocking operations.

Micronaut 2.x now leaves it up to the user to specify the thread pool to schedule operations on, including blocking operations.

If you are upgrading from Micronaut 1.x and have a controller that executes blocking I/O operations (such as JDBC or JPA) it is recommended that you add the@ExecuteOn annotation to execute the blocking operations on a different thread.

Using @ExecuteOn

import io.micronaut.http.annotation.*;
import io.micronaut.scheduling.TaskExecutors;
import io.micronaut.scheduling.annotation.ExecuteOn;

@Controller("/executeOn/people")
public class PersonController {

    private final PersonService personService;

    PersonController(
            PersonService personService) {
        this.personService = personService;
    }

    @Get("/{name}")
    @ExecuteOn(TaskExecutors.IO) <b>(1)</b>
    Person byName(String name) {
        return personService.findByName(name);
    }
}

Using @ExecuteOn

import io.micronaut.http.annotation.*
import io.micronaut.scheduling.TaskExecutors
import io.micronaut.scheduling.annotation.ExecuteOn

@Controller("/executeOn/people")
class PersonController {

    private final PersonService personService

    PersonController(
            PersonService personService) {
        this.personService = personService
    }

    @Get("/{name}")
    @ExecuteOn(TaskExecutors.IO) <b>(1)</b>
    Person byName(String name) {
        return personService.findByName(name)
    }
}

Using @ExecuteOn

import io.micronaut.http.annotation.*
import io.micronaut.scheduling.TaskExecutors
import io.micronaut.scheduling.annotation.ExecuteOn

@Controller("/executeOn/people")
class PersonController (private val personService: PersonService) {
    @Get("/{name}")
    @ExecuteOn(TaskExecutors.IO) <b>(1)</b>
    fun byName(name: String): Person {
        return personService.findByName(name)
    }
}
1 The@ExecuteOn annotation is used to execute the operation on the I/O thread pool
If you wish to return to the previous behaviour as defined in Micronaut 1.x you can set micronaut.server.thread-selection to AUTO in configuration.

Micronaut Cache Removed from Core

The micronaut-runtime module in Micronaut 1.x included support for caching AOP with the @Cache annotation. The core caching library and a default implementation based on caffeine were included.

If you are using the caffeine backed cache support, you must add a dependency to continue doing so. Add io.micronaut.cache:micronaut-cache-caffeine to your build to maintain the existing functionality. The new caffeine cache module no longer uses the internal shadowed caffeine version and declares its own normal dependency on caffeine. You may need to exclude the transitive dependency in order to prevent conflicts with other versions that may be being pulled in.

The management endpoint for caches is no longer included by default in the micronaut-management module. If you would like to use the caches endpoint, add a dependency on io.micronaut.cache:micronaut-cache-management .

Support for RxJava 1.x Removed from Core

RxJava 1.x support is no longer included in core by default. You can re-enable support by adding the following dependency:

implementation("io.micronaut.rxjava1:micronaut-rxjava1")
<dependency>
    <groupId>io.micronaut.rxjava1</groupId>
    <artifactId>micronaut-rxjava1</artifactId>
</dependency>

Support for Reactor Removed from Core

Reactor support is no longer included in core by default. You can re-enable support by adding the following dependency:

implementation("io.micronaut.reactor:micronaut-reactor")
<dependency>
    <groupId>io.micronaut.reactor</groupId>
    <artifactId>micronaut-reactor</artifactId>
</dependency>

Loggers Endpoint Modifications Now Sensitive

The method on the loggers endpoint to modify logging is now sensitive by default. To revert to the previous behavior, apply the following configuration:

endpoints:
    loggers:
        write-sensitive: false

Server Side Content Negotiation May Require Test Changes

The new support for server side content negotiation may require changes to tests. For example a test that that makes a call such as:

String result = client.toBlocking().retrieve(
    HttpRequest.GET('/test')
        .accept("text/plain"), String
)

If the server implementation does not declare the route as @Produces("text/plain") the request will be rejected with a NOT_ACCEPTABLE HTTP status code.

Server Filter Ordering

In Micronaut 1.x almost all built in filters operated on order 0, meaning the order was non deterministic. That caused some compatibility issues with the metrics and security modules. A new class has been addedServerFilterPhase that defines the phases of server filtering and sets an order. Many filters have been updated to now have their order reflect the phase they participate in.

Heartbeat Events No Longer Published if no Discovery Clients present

AHeartbeatEvent is no longer published automatically if there are noDiscoveryClient instances present.

This behaviour can be changed by setting micronaut.heartbeat.enabled to true explicitly in configuration.

Several TypeConverters Are No Longer Beans

Many TypeConverter instances that were previously beans are no longer beans and are deprecated including:

These should not be injected directly but instead used through theConversionService API.

@Executable No Longer Stereotype of @Around

In Micronaut 1.x the@Executable annotation was a meta-annotation specified on the@Around and@Introduction annotations. This meant that anExecutableMethod reference was generated for every usage of AOP advice which consumed additional memory unnecessarily.

In Micronaut 2.x and above this is no longer the case and if you need anExecutableMethod generated you should add the@Executable annotation to any custom AOP advice you specify otherwise the method will not be available via theBeanDefinition interface (using for example thegetExecutableMethods method).

Spot Bugs Instead of JSR-305 Nullable/NonNull Annotations

The JSR-305 annotations library ( com.google.code.findbugs:jsr305 ) is no longer a dependency (replaced by spotbugs-annotations ). If you need this library you will need to add it manually.

Events Renamed

The following events were renamed to avoid confusion with other events of similar names:

Old Name New Name

io.micronaut.discovery.event.ServiceStartedEvent

ServiceReadyEvent

io.micronaut.discovery.event.ServiceShutdownEvent

ServiceStoppedEvent

New Package for Netty Specific Classes of HTTP Client

The HTTP client implementation classes includingDefaultHttpClient (considered internal in Micronaut 1.x) have been moved to a sub-package called io.micronaut.http.client.netty .

HTTP Clients No Longer Named Beans

HTTP clients declared with micronaut.http.services (seeManual Service Discovery Configuration) are no longer named beans in the context and cannot be injected with javax.inject.Named , for example given the configuration:

Manually configuring services

micronaut:
    http:
        services:
            foo:
                urls:
                    - http://foo1
                    - http://foo2

You can no longer inject an HTTP client with @Named("foo") :

@Inject
@Named("foo")
RxHttpClient httpClient;

Instead you should always use@Client:

@Inject
@Client("foo")
RxHttpClient httpClient;

Source Retention Annotations No Longer Retained in Runtime Metadata

In Micronaut 1.x annotations specified as source retention were still retained in theAnnotationMetadata interface. As of Micronaut 2.x this is no longer the case with source retention annotations only available within the compiler APIs.

If you wish to retain a particular source level annotation when upgrading you can write an AnnotationTransformer that alters the RetentionPolicy of the annotation.

Iterable Beans No Longer Have An Implicit Primary

In Micronaut 1.x injecting a single instance of an iterable bean without qualifiers would inject the first bean. An iterable bean is typically anything annotated with @EachProperty or @EachBean . Those beans typically are referenced from configuration. The first bean in this context is the first item in configuration that matches what the annotation expects.

For example if you created a bean with @EachProperty("cars") , then specified the following in your config:

cars:
    ford:
        cylinders: 8
    subaru:
        cylinders: 4

Requesting a single instance of that bean would result in the "ford" instance being injected. Because that behavior is surprising and inconsistent with other types of beans, that is no longer the case and a NonUniqueBeanException will be thrown.

This change does not apply to an explicit primary defined in the annotation ( @EachProperty(value = "cars", primary = "ford") ), nor requesting the instance by a qualifier ( @Named("ford") CarConfig carConfig ).

No Longer Possible to Return Null to Disable a Bean

It is no longer possible to return null from a@Factory bean method to disable the bean. You should now throw DisabledBeanException instead.

Invalid Configuration File Locations

Specifying a file with micronaut.config.files , either through the system property or environment variable, that does not exist or cannot be read will now result in the application failing to startup. In previous versions of Micronaut a warning would be logged and the file would be ignored.

PropertySourceLoader Changes

Some default interface methods are no longer default and require implementation.

Deprecation Removal

Most if not all deprecated classes and methods have been removed.

Map Property Binding

In Micronaut 1.x java.util.Map properties being bound from config were inconsistently bound as either a nested or flat. Now maps are bound as nested by default and the@MapFormat annotation’s default value for transformation has been changed to reflect that.

For example given the config:

persons:
  joe:
    age: 30
  sally:
    age: 25

A map property injected via @Property(name ="persons") may have been injected flat or nested depending on a couple factors.

Flat

{"joe.age": 30, "sally.age": 25}

Nested

{"joe": {"age": 30}, "sally": {"age": 25}}

To bind to a map with flat keys, add the@MapFormat annotation and set the transformation member.

GraalVM BOM Entry

The no longer used group for Graal is no longer part of the bom. While upgrading if you depend on Graal you may see Could not find com.oracle.substratevm:svm:. . To resolve the issue, change the dependency group to org.graalvm.nativeimage .

@Retryable and @CircuitBreaker Exception Handling

@Retryable and @CircuitBreaker in previous versions of Micronaut resolved includes and excludes explicitly. Any exception thrown had to exactly match one of the exceptions specified. This has been changed to now also include subclasses of the exception types specified.

MessageSource API Changes

The semantics of the getMessage method have been changed to also interpolate the message with any provided variables. In previous versions of Micronaut, the raw message was returned from the bundle. To support reading the raw message, a new method getRawMessage has been added.

In addition, escaping of messages with single quotes is now implemented in accordance with the standard Java MessageFormat class. Messages that contain a single quote will now need escaping in order for the quote to output as it did previously. For example:

my.message=We love Micronaut's documentation

Would now be output as We love Micronauts documentation . To achieve the desired result, escape the single quote with another single quote.

my.message=We love Micronaut''s documentation
This change also applies to messages in custom constraint annotations, which interpolate the message via the message source api.

Environment Order Bugfix

Environments specified through the application context builder have priority over environments deduced or supplied through the MICRONAUT_ENVIRONMENTS environment variable, or the equivalent system property. A bug in the logic however did not change the order of a specified environment if it previously was found or deduced. This issue manifested itself with @MicronautTest(environments = "test") . The test environment is already deduced for tests, so it retained the order of other deduced environments, and was able to be overriden by MICRONAUT_ENVIRONMENTS=dev . In Micronaut 1.x configuration for dev would have overridden test . In Micronaut 2.x test will override dev .

Introspections and Inner Classes

A bug in Micronaut 1.x caused bean introspections to be generated for inner classes of classes annotated with @Introspected . That also applies to classes where @Introspection is a meta annotation, like @Entity . This may have an impact for GraalVM users that rely on accesses to those classes without using reflection. For example:

@Entity
public class Pet {
    ...
    private PetType type = PetType.DOG;
    // getters & setters

    public enum PetType {
        DOG,
        CAT
    }
}

Previously a bean introspection would have been generated for PetType . That is no longer the case. If the type should be introspected, simply add the annotation.

Executable Methods

A bug in Micronaut 1.x caused bean definitions to be created for classes that only declared executable annotations on one or more methods. Classes with executable methods must now be explicitly declared as a bean with a scope annotation in order for a bean definition to be created.

Super interfaces No Longer Searched for Fallbacks

If the@Fallback annotation is used to specify a fallback for a@Client interface, the super interfaces of the client will no longer be traversed to lookup the fallback to invoke.

In this case it may be necessary to specify the api member of the@Recoverable annotation to ensure the fallback can be looked up. For example:

Specifying the api to recover from
@Client("/Books")
@Recoverable(api = BookApi)
interface BookClient extends BookApi {
    @Override
    Book get(Long id);
}

AWS FunctionClient Moved to AWS Module

The AWS based function client has been made part of the Micronaut AWS project. If you need this functionality add the following dependency:

implementation("io.micronaut.aws:micronaut-function-client-aws")
<dependency>
    <groupId>io.micronaut.aws</groupId>
    <artifactId>micronaut-function-client-aws</artifactId>
</dependency>

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

社会再平衡

社会再平衡

[加] 亨利·明茨伯格 / 陆维东、鲁强 / 东方出版社 / 2015-9 / 38.00元

明茨伯格曾坦言:我虽然不是律师,但我觉得有必要质疑法律的失效;我也不算是经济学家,但我觉得有义务来挑战一切事物以经济为指标的标准;我也不是人类学家、社会学家、心理学家,或者政治科学,更不是活动分子,但是在我的讨论中,文化、行为、权力、社会运动都扮演了重要的角色。我是一个合成者,我最成功的书都囊括了不同来源的想法。 明茨伯格创作《社会再平衡》这本书的初衷是因为关注身边的趋势:环境的恶化、民主的......一起来看看 《社会再平衡》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试