(Quick Reference)

3 Inversion of Control

Version: 1.3.5

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.18" // <1>
}

...
dependencies {
    annotationProcessor "io.micronaut:micronaut-inject-java:1.3.5" // <2>
    compile "io.micronaut:micronaut-inject:1.3.5"
    ...
    testAnnotationProcessor "io.micronaut:micronaut-inject-java:1.3.5" // <3>
    ...
}
1 Apply the Annotation Processing plugin
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 the ApplicationContext interface, which includes a run method. The following example demonstrates using it:

Running the ApplicationContext
try (ApplicationContext context = ApplicationContext.run()) { (1)
    MyBean myBean = context.getBean(MyBean.class); (2)
    // do something with your bean
}
1 Run the ApplicationContext
2 Retrieve a bean that has been dependency injected
The example uses Java’s try-with-resources syntax to ensure the ApplicationContext 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 { (1)
    int getCylinders();
    String start();
}

@Singleton(2)
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) {(3)
        this.engine = engine;
    }

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

@Singleton (2)
class V8Engine implements Engine {
    int cylinders = 8

    String start() {
        "Starting V8"
    }
}

@Singleton
class Vehicle {
    final Engine engine

    Vehicle(Engine engine) { (3)
        this.engine = engine
    }

    String start() {
        engine.start()
    }
}
interface Engine {
    (1)
    val cylinders: Int

    fun start(): String
}

@Singleton(2)
class V8Engine : Engine {

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

@Singleton
class Vehicle(private val engine: Engine)(3)
{

    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 the BeanContext 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 the BeanDefinition 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

The BeanContext 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 { (1)
    int getCylinders();
    String start();
}

@Singleton
public class V6Engine implements Engine {  (2)
    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) {(4)
        this.engine = engine;
    }

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

@Singleton
class V6Engine implements Engine { (2)
    int cylinders = 6

    String start() {
        "Starting V6"
    }
}

@Singleton
class V8Engine implements Engine { (3)
    int cylinders = 8

    String start() {
        "Starting V8"
    }
}

@Singleton
class Vehicle {
    final Engine engine

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

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

@Singleton
class V6Engine : Engine {  (2)

    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)(4)
{

    fun start(): String {
        return engine.start()(5)
    }
}
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 Named annotation is used to indicate the V8Engine implementation is required
5 Calling the start method prints: "Starting V8"

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) { (1)
        this.colorPicker = colorPicker;
    }

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

    protected final ColorPicker colorPicker

    TestController(ColorPicker colorPicker) { (1)
        this.colorPicker = colorPicker
    }

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

    @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 a Secondary 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 the CustomScope interface.

Note that with Micronaut when starting an ApplicationContext 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 the ApplicationContext. In other words when the ApplicationContext 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.2 Refreshable Scope

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

The following example, illustrates the @Refreshable scope behavior.

@Refreshable (1)
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());(2)
    }

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

    String forecast

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

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

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

    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 a RefreshEvent 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 ) (1)
@Singleton (2)
@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 ) (1)
@Singleton (2)
@Documented
@Retention(RUNTIME)
@interface Driver {
}
import io.micronaut.context.annotation.Requires
import javax.inject.Singleton


@Requires(classes = [Car::class]) (1)
@Singleton (2)
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class Driver
1 The scope declares a requirement on a Car class using Requires
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 the DefaultScope 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) (1)
@Documented
@Retention(RUNTIME)
public @interface Driver {
}
@Requires(classes = Car.class )
@DefaultScope(Singleton.class) (1)
@Documented
@Retention(RUNTIME)
@interface Driver {
}
@Requires(classes = [Car::class])
@DefaultScope(Singleton::class) (1)
@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 the Factory 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 on bean scopes for 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.

Nullability

Factory methods can return null to allow for beans to be created conditionally. The use of @Requires should always be the preferred method to conditionally create beans and returning null from 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 {
            return null;
        }
    }
}
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 {
            null
        }
    }
}
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 {
            null
        }
    }
}

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.

The Requires 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") (1)
@Requires(property="foo", value="John") (2)
@Requires(property="foo", value="John", defaultValue="John") (3)
@Requires(property="foo", notEquals="Sally") (4)
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 a Qualifier. 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 for Conditional Beans and 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) (1)
@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) (1)
@Singleton
class MockBookService implements BookService {

    Map<String, Book> bookMap = [:]

    @Override
    Book findBook(String title) {
        bookMap.get(title)
    }
}
Using @Replaces
@Replaces(JdbcBookService::class) (1)
@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; (1)
import javax.inject.Singleton;

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

    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 (3)
    public void initialize() {
        this.initialized = true;
    }
}
import javax.annotation.PostConstruct (1)
import javax.inject.Singleton

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

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

        return "Starting V8"
    }

    @PostConstruct (3)
    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 (2)

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

        return "Starting V8"
    }

    @PostConstruct (3)
    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; (1)
import javax.inject.Singleton;
import java.util.concurrent.atomic.AtomicBoolean;

@Singleton
public class PreDestroyBean implements AutoCloseable {

    AtomicBoolean stopped = new AtomicBoolean(false);

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

@Singleton
class PreDestroyBean implements AutoCloseable {

    AtomicBoolean stopped = new AtomicBoolean(false)

    @PreDestroy (2)
    @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 (2)
    @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 the Bean 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") (1)
    @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") (1)
    @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") (1)
    @Singleton
    fun connection(): Connection {
        return Connection()
    }
}

import java.util.concurrent.atomic.AtomicBoolean;

public class Connection {

    AtomicBoolean stopped = new AtomicBoolean(false);

    public void stop() { (2)
        stopped.compareAndSet(false, true);
    }

}
import java.util.concurrent.atomic.AtomicBoolean

class Connection {

    AtomicBoolean stopped = new AtomicBoolean(false)

    void stop() { (2)
        stopped.compareAndSet(false, true)
    }

}
import java.util.concurrent.atomic.AtomicBoolean

class Connection {

    internal var stopped = AtomicBoolean(false)

    fun stop() { (2)
        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 extend ApplicationEvent.

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()
    }
}
The supports 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 like StartupEvent and ShutdownEvent:

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 with Factory beans. Consider the following example:

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

    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); (2)
    }

    @Singleton
    public Engine v8Engine() {
        return engine;(3)
    }

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

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

    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) (2)
    }

    @Singleton
    Engine v8Engine() {
        return engine (3)
    }
}

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

    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) (2)
    }

    @Singleton
    fun v8Engine(): Engine? {
        return engine(3)
    }

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

@Singleton
class EngineInitializer : BeanInitializedEventListener<EngineFactory> { (4)
    override fun onInitialized(event: BeanInitializingEvent<EngineFactory>): EngineFactory {
        val engineFactory = event.bean
        engineFactory.setRodLength(6.6)(5)
        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 a Bean
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.

The BeanIntrospector and BeanIntrospection 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:1.3.5")
<annotationProcessorPaths>
    <path>
        <groupId>io.micronaut</groupId>
        <artifactId>micronaut-inject-java</artifactId>
        <version>1.3.5</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:1.3.5")
<dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-core</artifactId>
    <version>1.3.5</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 the BeanIntrospection API:

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

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

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

        val property : BeanProperty<Person, String> = introspection.getRequiredProperty("name", String::class.java) (3)
        property.set(person, "Fred") (4)
        val name = property.get(person) (5)
        print("Hello ${person.name}")
1 You can retrieve a BeanIntrospection with the getIntrospection static method
2 Once you have a BeanIntrospection you can instantiate a bean with the instantiate method.
3 A BeanProperty 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 (1)
    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 (1)
    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) { (1)

    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 (1)
    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 (1)
    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 (1)
        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 an AnnotationMapper.

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

A BeanProperty 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 the BeanWrapper 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")); (1)

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

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

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

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

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

        println("Person's age now $newAge")
1 The getWrapper static method can be used to obtain a BeanWrapper for a bean instance.
2 You can set properties and the BeanWrapper 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 the BeanIntrospection 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 use ConstraintValidator (io.micronaut.validation.validator.constraints.ConstraintValidator) to define custom constraints, which supports validating annotations as 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.configuration:micronaut-hibernate-validator")
<dependency>
    <groupId>io.micronaut.configuration</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("") (1)
        );

        assertEquals("sayHello.name: must not be blank", exception.getMessage()); (2)
    }
}
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("") (1)

        then:"A validation error occurs"
        def e = thrown(ConstraintViolationException)
        e.message == "sayHello.name: must not be blank" //  (2)
    }
}
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("") } (1)

        assertEquals("sayHello.name: must not be blank", exception.message) (2)
    }
}
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 Bean Introspection) 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 of Validator:

@Inject
Validator validator;

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

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

    assertEquals(2, constraintViolations.size()); (2)
}
@Inject Validator validator

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

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

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

    assertEquals(2, constraintViolations.size) (2)
}
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) (1)
            );

    assertEquals(2, exception.getConstraintViolations().size()); (2)
}
@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)) (1)

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

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

    assertEquals(2, exception.constraintViolations.size) (2)
}
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 = { }) (1)
public @interface DurationPattern {

    String message() default "invalid duration ({validatedValue})"; (2)

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

import static java.lang.annotation.RetentionPolicy.RUNTIME

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

@Retention(AnnotationRetention.RUNTIME)
@Constraint(validatedBy = []) (1)
annotation class DurationPattern(
    val message: String = "invalid duration ({validatedValue})" (2)
)
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 the MessageSource interface. (optional)
3 To support repeated annotations you can define a inner annotation (optional).
You can add messages and message bundles using the MessageSource and ResourceBundleMessageSource classes.

Once you have defined the annotation you need to implement a ConstraintValidator 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")   (1)
            );

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

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

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

    assertEquals("startHoliday.duration: invalid duration (junk)", exception.message) (2)
}
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 io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.validation.validator.constraints.*;
import javax.annotation.*;

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 io.micronaut.core.annotation.AnnotationValue
import io.micronaut.validation.validator.constraints.*
import javax.annotation.*

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.

The BeanContext API can be used to obtain a reference to a BeanDefinition which implements the AnnotationMetadata 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 all BeanDefinition '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 an AnnotationValue 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") (1)
    String value() default "";

    /**
     * @return The ID of the client
     */
    @AliasFor(member = "value") (2)
    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 an AnnotationMapper. To create an AnnotationMapper you must perform the following steps:

  • Implement the AnnotationMapper 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) { (1)
        final AnnotationValueBuilder<Introspected> builder = AnnotationValue.builder(Introspected.class)
                // don't bother with transients properties
                .member("excludedAnnotations", "javax.persistence.Transient"); (2)
        return Collections.singletonList(
                builder.build()
        );
    }
}
1 The map method receives a AnnotationValue 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:

@Configuration
class ByAnnotationTypeConfig {

    @Bean
    MicronautBeanProcessor beanProcessor() {
        new MicronautBeanProcessor(Prototype, Singleton)
    }
}

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:1.3.5"
    compileOnly "io.micronaut:micronaut-inject-java:1.3.5"
    ...
}

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 the ApplicationContext.

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 { (1)

    private ApplicationContext ctx;

    public BaseApplication() {
        super();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        ctx = ApplicationContext.run(MainActivity.class, Environment.ANDROID); (2)
        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { (3)
            @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