@Configuration and @ConfigurationBuilder

Learn how to utilize @Configuration and @ConfigurationBuilder annotations to effectively configure declared properties

Authors: Nirav Assar

Micronaut Version: 1.2.7

1 Getting Started

In this guide you are going to learn how to effectively use the annotations @ConfigurationProperties, @ConfigurationBuilder, and @EachProperty to use configured properties in a Micronaut application. These annotations allow declared values to be injected into a bean for easy usage in the application.

1.1 What you will need

To complete this guide, you will need the following:

  • Some time on your hands

  • A decent text editor or IDE

  • JDK 1.8 or greater installed with JAVA_HOME configured appropriately

1.2 How to complete the guide

We recommend you to follow the instructions in the next sections and create the app step by step. However, you can go right to the completed example.

or

Then, cd into the complete folder which you will find in the root project of the downloaded/cloned project.

2 Writing the Application

Create a Groovy Micronaut app using the Micronaut Command Line Interface.

mn create-app example.micronaut.complete --lang=groovy

The previous command creates a micronaut app with the default package example.micronaut in a folder named complete.

Due to the --lang groovy flag, it generates a Groovy Micronaut app that uses the Gradle build system. However, you could use other build tools such as Maven or other programming languages such as Java or Kotlin.

3 Team Configuration with @ConfigurationProperties

Imagine a feature where you can configure a sports team in a declarative manner. The team has a few attributes like team name, color, and players.

src/main/resources/application.yml
team:
  name: 'Steelers'
  color: 'Black'
  player-names: ['Mason Rudolph', 'James Connor']

With Micronaut we can use the @ConfigurationProperties annotation to slurp the configuration into a bean. Each property that matches the configuration in the application.yml will call the setter in the bean. The bean will be subsequently available for injection in the application!

src/main/groovy/micronaut/configuration/groovy/TeamConfiguration.groovy
@ConfigurationProperties("team")
class TeamConfiguration {
    String name
    String color
    List<String> playerNames
}

3.1 Test @ConfigurationProperties

Let’s validate that the bean is available in the application context and is created with the values declared in the application.xml.

src/test/groovy/micronaut/configuration/groovy/TeamConfigurationSpec.groovy
    void "test team configuration"() {
        given:
        ApplicationContext ctx = ApplicationContext.run(ApplicationContext, [ (1)
                "team.name": 'evolution',
                "team.color": 'green',
                "team.player-names": ['Nirav Assar', 'Lionel Messi']
        ])

        when:
        TeamConfiguration teamConfiguration = ctx.getBean(TeamConfiguration)

        then:
        teamConfiguration.name == "evolution"
        teamConfiguration.color == "green"
        teamConfiguration.playerNames[0] == "Nirav Assar"
        teamConfiguration.playerNames[1] == "Lionel Messi"

        cleanup:
        ctx.close()
    }
}
1 Setup configuration properties for the test to use

4 Team Admin Builder with @ConfigurationBuilder

The Builder pattern is a great way to build configuration objects incrementally. Read about the Builder pattern in this DZone article to learn more. Micronaut supports the Builder pattern with @ConfigurationBuilder.

Let’s suppose we want to add team administrators to a team. The team administration is composed by using a builder pattern object. We can add a coach, manager and president to the team.

src/main/resources/application.yml
team:
  name: 'Steelers'
  color: 'Black'
  player-names: ['Mason Rudolph', 'James Connor']
  team-admin:
    manager: 'Nirav Assar' (1)
    coach: 'Mike Tomlin'
    president: 'Dan Rooney'
1 manager property is an example of an element that will be built

The TeamAdmin object abides by the Builder pattern.

src/main/groovy/micronaut/configuration/groovy/TeamAdmin.groovy
package micronaut.configuration.groovy

import groovy.transform.CompileStatic

@CompileStatic
class TeamAdmin { (1)

    String manager
    String coach
    String president

    // should use the builder pattern to create the object
    private TeamAdmin() {
    }

    static Builder builder() {
        return new Builder()
    }

    static class Builder { (2)
        String manager
        String coach
        String president

        (3)
        Builder withManager(String manager) {
            this.manager = manager
            this
        }

        Builder withCoach(String coach) {
            this.coach = coach
            this
        }

        Builder withPresident(String president) {
            this.president = president
            this
        }

        TeamAdmin build() { (4)
            TeamAdmin teamAdmin = new TeamAdmin()
            teamAdmin.manager = this.manager
            teamAdmin.coach = this.coach
            teamAdmin.president = this.president
            teamAdmin
        }
    }
}
1 TeamAdmin is the configuration object which consumes the declared properties.
2 The builder object is used to incrementally construct the object.
3 An example of a builder method, where a attribute is set and then the builder itself is returned.
4 The final build() method creates the TeamAdmin object.

At the bottom of TeamConfiguration, we add the inner class TeamAdmin.Builder and annotate it with @ConfigurationBuilder. This tells Micronaut that configuration can be read in and an object can be constructed using the Builder pattern.

We are using the builder only here, so we will have to call builder.build() to actually get the TeamAdmin object, at a later time. In our case, we will call builder.build() in the spock test.
src/main/groovy/micronaut/configuration/groovy/TeamConfiguration.groovy
@ConfigurationProperties("team")
class TeamConfiguration {
    String name
    String color
    List<String> playerNames

    @ConfigurationBuilder(prefixes = "with", configurationPrefix = "team-admin") (1)
    TeamAdmin.Builder builder = TeamAdmin.builder() (2)
}
1 prefixes tells Micronaut to find methods that are prefixed by with; configurationPrefix allows the developer to customize the application.yml element
2 Instantiate the builder object so it can be populated with configuration values.

4.1 Test @ConfigurationBuilder

We can validate @ConfigurationBuilder is applied properly with the following spock test. The test format is similar to previous tests.

src/test/groovy/micronaut/configuration/groovy/TeamConfigurationSpec.groovy
    void "test team configuration admin configuration builder "() {
        given:
        ApplicationContext ctx = ApplicationContext.run(ApplicationContext, [
                "team.name": 'evolution',
                "team.color": 'green',
                "team.player-names": ['Nirav Assar', 'Lionel Messi'],
                "team.team-admin.manager": "Jerry Jones", (1)
                "team.team-admin.coach": "Tommy O'Neill",
                "team.team-admin.president": "Mark Scanell"
        ])

        when:
        TeamConfiguration teamConfiguration = ctx.getBean(TeamConfiguration)
        TeamAdmin teamAdmin = teamConfiguration.builder.build() (2)

        then:
        teamConfiguration.name == "evolution"
        teamConfiguration.color == "green"
        teamConfiguration.playerNames[0] == "Nirav Assar"
        teamConfiguration.playerNames[1] == "Lionel Messi"

        // check the builder has values set
        teamConfiguration.builder.manager == "Jerry Jones"
        teamConfiguration.builder.coach == "Tommy O'Neill"
        teamConfiguration.builder.president == "Mark Scanell"

        // check the object can be built
        teamAdmin.manager == "Jerry Jones" (3)
        teamAdmin.coach == "Tommy O'Neill"
        teamAdmin.president == "Mark Scanell"

        cleanup:
        ctx.close()
    }
1 Properties which will invoke the builder methods on TeamAdmin.Builder
2 The builder object is now configured, so we must run build() on it to create the TeamAdmin object
3 Verify the object is created with the applicaton.yml properties

5 Stadiums with @EachProperty

Micronaut is also able to read a "list" of configurations that are related. Imagine we would like to declare stadiums and their attributes.

src/main/resources/application.yml
stadium:
  coors: (1)
    city: 'Denver'
    size: 50000
  pnc:
    city: 'Pittsburgh'
    size: 35000
1 This element will be the name of the bean.

We can use @EachProperty which will cycle through the configuration and read each nested clause as a bean. The higher level property will be parameterized as the name.

src/main/groovy/micronaut/configuration/groovy/StadiumConfiguration.groovy
package micronaut.configuration.groovy


import io.micronaut.context.annotation.EachProperty
import io.micronaut.context.annotation.Parameter

@EachProperty("stadium") (1)
class StadiumConfiguration {
    String name (2)
    String city
    Integer size

    StadiumConfiguration(@Parameter String name) { (2)
        this.name = name
    }
}
1 Establish the top layer of configuration
2 name is read in from the property key and send as a parameter to the bean.

5.1 Test @EachProperty

Validate the configuration with a test. Notice multiple beans are created from the configuration. In a controller we can inject a particular StadiumConfiguration instance bean by using the @Named parameter with qualifier name.

src/test/groovy/micronaut/configuration/groovy/StadiumConfigurationSpec.groovy
package micronaut.configuration.groovy

import io.micronaut.context.ApplicationContext
import io.micronaut.inject.qualifiers.Qualifiers
import spock.lang.Specification

class StadiumConfigurationSpec extends Specification {

    void "test stadium configuration"() {
        given:
        ApplicationContext ctx = ApplicationContext.run(ApplicationContext, [
                "stadium.fenway.city": 'Boston', (1)
                "stadium.fenway.size": 60000,
                "stadium.wrigley.city": 'Chicago',
                "stadium.wrigley.size": 45000
        ])

        when:
        (2)
        StadiumConfiguration fenwayConfiguration = ctx.getBean(StadiumConfiguration, Qualifiers.byName("fenway"))
        StadiumConfiguration wrigleyConfiguration = ctx.getBean(StadiumConfiguration, Qualifiers.byName("wrigley"))

        then:
        fenwayConfiguration.name == "fenway"
        fenwayConfiguration.size == 60000
        wrigleyConfiguration.name == "wrigley"
        wrigleyConfiguration.size == 45000

        cleanup:
        ctx.close()
    }
}
1 Multiple configurations can be declared for the same class.
2 Since there are multiple beans to retrieve a bean a Qualifier must be sent.

6 Running the Application

To run the application use the ./gradlew run command which will start the application on port 8080.

7 Controller

Configuration beans can be injected into the application with just like any other beans. As a demonstration, create a controller where the beans are constructor injected. The StadiumConfiguration class has two instances, so for injection we need to use the @Named annotation with a qualifier name to specify the bean.

src/main/groovy/micronaut/configuration/groovy/MyController.groovy
package micronaut.configuration.groovy

import groovy.transform.CompileStatic
import io.micronaut.http.HttpResponse
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get

import javax.annotation.Nullable
import javax.inject.Named

@CompileStatic
@Controller("/my")
class MyController {

    TeamConfiguration teamConfiguration
    StadiumConfiguration stadiumConfiguration

    MyController(TeamConfiguration teamConfiguration,
                 @Named("pnc") StadiumConfiguration stadiumConfiguration) { (1)
        this.teamConfiguration = teamConfiguration
        this.stadiumConfiguration = stadiumConfiguration
    }

    @Get("/team")
    TeamConfiguration team() {
        this.teamConfiguration
    }

    @Get("/stadium")
    StadiumConfiguration stadium() {
        this.stadiumConfiguration
    }
}
1 Injection of configuration beans; @Named annotation is needed to choose which StadiumConfiguration instance is retrieved.
In the browser go to http://localhost:8080/my/team and http://localhost:8080/my/stadium. To test the app, run ./gradlew test

8 Where To Go From Here?

9 Help with Micronaut

OCI sponsored the creation of this Guide. OCI offers several Micronaut services:

Free consultation

The OCI Micronaut Team includes Micronaut co-founders, Jeff Scott Brown and Graeme Rocher. Check our Micronaut courses and learn from the engineers who developed, matured and maintain Micronaut.

Micronaut OCI Team