mn create-app example.micronaut.micronautguide \
--features=yaml,serialization-jackson \
--build=gradle \
--lang=groovy \
--test=spock
Table of Contents
- 1. Getting Started
- 2. What you will need
- 3. Solution
- 4. Writing the Application
- 5. Team Configuration with @ConfigurationProperties
- 6. Team Admin Builder with @ConfigurationBuilder
- 7. Stadiums with @EachProperty
- 8. Running the Application
- 9. Controller
- 10. Next Steps
- 11. Help with the Micronaut Framework
- 12. License
@Configuration and @ConfigurationBuilder
Learn how to utilize @Configuration and @ConfigurationBuilder annotations to effectively configure declared properties.
Authors: Nirav Assar
Micronaut Version: 4.6.3
1. Getting Started
In this guide, we will create a Micronaut application written in Groovy.
In this guide you will 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.
2. What you will need
To complete this guide, you will need the following:
-
Some time on your hands
-
A decent text editor or IDE (e.g. IntelliJ IDEA)
-
JDK 21 or greater installed with
JAVA_HOME
configured appropriately
3. Solution
We recommend that you follow the instructions in the next sections and create the application step by step. However, you can go right to the completed example.
-
Download and unzip the source
4. Writing the Application
Create an application using the Micronaut Command Line Interface or with Micronaut Launch.
If you don’t specify the --build argument, Gradle with the Kotlin DSL is used as the build tool. If you don’t specify the --lang argument, Java is used as the language.If you don’t specify the --test argument, JUnit is used for Java and Kotlin, and Spock is used for Groovy.
|
The previous command creates a Micronaut application with the default package example.micronaut
in a directory named micronautguide
.
If you use Micronaut Launch, select Micronaut Application as application type and add yaml
, and serialization-jackson
features.
If you have an existing Micronaut application and want to add the functionality described here, you can view the dependency and configuration changes from the specified features, and apply those changes to your application. |
5. 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.
team:
name: 'Steelers'
color: 'Black'
player-names: ['Mason Rudolph', 'James Connor']
With the Micronaut framework, 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!
@ConfigurationProperties("team")
class TeamConfiguration {
String name
String color
List<String> playerNames
5.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.yml
.
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 | Set up configuration properties for the test to use |
6. 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. The Framework 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.
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.
package example.micronaut
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 the Micronaut framework 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 JUnit test.
|
package example.micronaut
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.micronaut.context.annotation.ConfigurationBuilder
import io.micronaut.context.annotation.ConfigurationProperties
import io.micronaut.serde.annotation.Serdeable
@Serdeable (1)
@JsonIgnoreProperties("builder") (2)
//tag::teamConfigClassNoBuilder[]
@ConfigurationProperties("team")
class TeamConfiguration {
String name
String color
List<String> playerNames
//end::teamConfigClassNoBuilder[]
@ConfigurationBuilder(prefixes = "with", configurationPrefix = "team-admin") (3)
TeamAdmin.Builder builder = TeamAdmin.builder() (4)
}
//tag::gettersandsetters[]
//end::gettersandsetters[]
1 | Declare the @Serdeable annotation at the type level in your source code to allow the type to be serialized or deserialized. |
2 | Mark the builder as being ignored during serialization. |
3 | prefixes tells the Micronaut framework to find methods that are prefixed by with ; configurationPrefix allows the developer to customize the application.yml element |
4 | Instantiate the builder object so it can be populated with configuration values. |
6.1. Test @ConfigurationBuilder
We can validate @ConfigurationBuilder
is applied properly with the following JUnit test. The test format is similar to previous tests.
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 |
7. Stadiums with @EachProperty
The Micronaut framework is also able to read a "list" of configurations that are related. Imagine we would like to declare stadiums and their attributes.
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.
package example.micronaut
import io.micronaut.context.annotation.EachProperty
import io.micronaut.context.annotation.Parameter
import io.micronaut.serde.annotation.Serdeable
@Serdeable (1)
@EachProperty("stadium") (2)
class StadiumConfiguration {
String name (3)
String city
Integer size
StadiumConfiguration(@Parameter String name) { (3)
this.name = name
}
}
1 | Declare the @Serdeable annotation at the type level in your source code to allow the type to be serialized or deserialized. |
2 | Establish the top layer of configuration |
3 | name is read in from the property key and send as a parameter to the bean. |
7.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.
package example.micronaut
import example.micronaut.StadiumConfiguration
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. |
8. Running the Application
To run the application, use the ./gradlew run
command, which starts the application on port 8080.
9. 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.
package example.micronaut
import groovy.transform.CompileStatic
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import jakarta.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. |
Add test:
package example.micronaut
import io.micronaut.http.HttpRequest
import io.micronaut.http.client.HttpClient
import io.micronaut.http.client.annotation.Client
import io.micronaut.test.extensions.spock.annotation.MicronautTest
import spock.lang.Specification
import jakarta.inject.Inject
@MicronautTest
class MyControllerSpec extends Specification {
@Inject
@Client("/")
HttpClient client
void 'test my team'() {
when:
TeamConfiguration teamConfiguration = client.toBlocking().retrieve(HttpRequest.GET("/my/team"), TeamConfiguration)
then:
teamConfiguration.name == 'Steelers'
teamConfiguration.color == 'Black'
and:
teamConfiguration.playerNames.size() == 2
teamConfiguration.playerNames == ['Mason Rudolph', 'James Connor']
}
}
10. Next Steps
Visit Micronaut Application Configuration to learn more.
11. Help with the Micronaut Framework
The Micronaut Foundation sponsored the creation of this Guide. A variety of consulting and support services are available.
12. License
All guides are released with an Apache license 2.0 license for the code and a Creative Commons Attribution 4.0 license for the writing and media (images…). |