Micronaut @ConfigurationBuilder Example
Micronaut is a modern, JVM-based framework designed for building lightweight, modular microservices. One of the key aspects of Micronaut is its powerful configuration system. Developers often need to map configuration properties from YAML or properties files into strongly typed Java classes. In our Micronaut @ConfigurationBuilder example we will see how configuration properties can be bound effectively in a Micronaut application.
1. What is Micronaut?
Micronaut is a modern, JVM-based, full-stack framework specifically designed for building modular, high-performance microservices, serverless applications, and cloud-native systems. It offers first-class support for Java, Kotlin, and Groovy, making it a flexible choice for JVM developers.
Unlike traditional Java frameworks that rely heavily on runtime reflection and proxies (such as Spring), Micronaut uses compile-time annotation processing for dependency injection, AOP (Aspect-Oriented Programming), and configuration. This unique approach drastically reduces memory usage and startup time, making it ideal for microservices and serverless environments like AWS Lambda or Google Cloud Functions.
Micronaut also integrates seamlessly with GraalVM to enable ahead-of-time (AOT) compilation into native executables, further improving performance and resource efficiency.
Key features of Micronaut include:
- Compile-time dependency injection: No reflection or runtime proxies, leading to better performance and smaller memory footprint.
- Fast startup time and low memory usage: Ideal for microservices and containerized environments.
- Seamless integration with GraalVM: Supports building native images for ultra-fast execution and minimal memory use.
- Reactive and non-blocking: Includes built-in support for reactive programming using RxJava, Project Reactor, etc.
- Built-in HTTP server/client: Lightweight Netty-based web server and declarative HTTP clients with easy configuration.
- Cloud-native readiness: Support for service discovery, distributed tracing, configuration management, and more.
- Modular architecture: Encourages separation of concerns and better maintainability.
1.1 What is @ConfigurationBuilder?
The @ConfigurationBuilder
annotation in Micronaut is used to bind configuration properties from application.yml
or application.properties
to a builder-style class within a @ConfigurationProperties
bean.
This is particularly useful when you want to populate a third-party class (like a builder) or a custom builder-style class without writing boilerplate setters. Instead of manually setting each field, Micronaut can automatically apply the configuration using compile-time processing, ensuring type safety and performance.
Key points include:
- Maps configuration values directly to a builder-style object, reducing the need for verbose setter methods.
- Must be used inside a class annotated with
@ConfigurationProperties
, which defines the root property path. - Supports method prefixes like
set
,with
, or even custom-defined prefixes to match the builder pattern of the target class. - Helps in configuring complex objects like
NettyServerBuilder
,OkHttpClient.Builder
, or any custom builder you define in your codebase. - Improves maintainability by separating configuration from implementation logic.
1.1.1 Benefits of Using @ConfigurationBuilder
Using @ConfigurationBuilder
provides several advantages in real-world applications:
- Integration with External Libraries: Easily bind configuration to builders from libraries like Netty, OkHttp, gRPC, etc.
- Cleaner Codebase: Reduces manual mapping and boilerplate code.
- Fluent Configuration: Works well with builder-style APIs and enhances readability.
- Compile-time safety: Errors are caught early, thanks to Micronaut’s annotation processing.
- Nested Config Support: Makes managing nested YAML structures intuitive.
1.1.2 Common Pitfalls and How to Avoid Them
While @ConfigurationBuilder
is powerful, developers can run into a few issues:
- Method Naming Mismatch: Make sure your builder methods follow a consistent naming pattern like
setXyz()
orwithXyz()
. Use themethods
orprefix
attributes of@ConfigurationBuilder
if necessary. - Missing Default Constructor: The builder class should have a default constructor, as Micronaut instantiates it directly.
- Not Using @ConfigurationProperties: Remember that
@ConfigurationBuilder
must be used within a class annotated with@ConfigurationProperties
. - Incorrect Prefix in YAML: Double-check the YAML structure and prefix mapping. Mistyped keys can silently fail.
2. Code Example
Now that we understand the basics, let’s explore a real-world Micronaut ConfigurationBuilder
example to see how configuration properties can be mapped from YAML into a builder-style Java class.
2.1 application.yml
app: name: MicronautApp retries: 5 pool: max-connections: 10 min-connections: 2
This YAML file defines configuration values under the root namespace app
. Nested inside is the pool
section, which holds values to be mapped using a builder-style class.
2.2 AppConfig.java (Builder-style class)
package example; public class AppConfig { private int maxConnections; private int minConnections; public AppConfig setMaxConnections(int maxConnections) { this.maxConnections = maxConnections; return this; } public AppConfig setMinConnections(int minConnections) { this.minConnections = minConnections; return this; } public int getMaxConnections() { return maxConnections; } public int getMinConnections() { return minConnections; } @Override public String toString() { return "AppConfig{" + "maxConnections=" + maxConnections + ", minConnections=" + minConnections + '}'; } }
This builder-style class follows the fluent pattern with setXyz()
methods that return this
. Micronaut will use these setters to populate the fields based on the pool
section from the YAML config.
2.3 MyConfig.java
package example; import io.micronaut.context.annotation.ConfigurationProperties; import io.micronaut.context.annotation.ConfigurationBuilder; @ConfigurationProperties("app") public class MyConfig { private String name; private int retries; @ConfigurationBuilder(prefix = "pool") private AppConfig appConfig = new AppConfig(); public String getName() { return name; } public void setName(String name) { this.name = name; } public int getRetries() { return retries; } public void setRetries(int retries) { this.retries = retries; } public AppConfig getAppConfig() { return appConfig; } }
This class is annotated with @ConfigurationProperties("app")
, which tells Micronaut to bind properties under the app
namespace. The @ConfigurationBuilder(prefix = "pool")
annotation maps the nested pool
properties to the AppConfig
builder using the matching setter methods.
2.4 Application.java
package example; import io.micronaut.runtime.Micronaut; import jakarta.inject.Inject; import jakarta.inject.Singleton; import javax.annotation.PostConstruct; @Singleton public class Application { @Inject MyConfig myConfig; @PostConstruct public void run() { System.out.println("App Name: " + myConfig.getName()); System.out.println("Retries: " + myConfig.getRetries()); System.out.println("AppConfig: " + myConfig.getAppConfig()); } public static void main(String[] args) { Micronaut.run(Application.class); } }
This simple application demonstrates how dependency injection is used to access MyConfig
. At runtime, Micronaut injects the populated config bean, and we print out the values to verify that everything was correctly mapped — including the nested builder-style AppConfig
object.
2.5 Code Output
App Name: MicronautApp Retries: 5 AppConfig: AppConfig{maxConnections=10, minConnections=2}
As shown in the output, all configuration values are correctly mapped — including those inside the builder class. This example highlights how @ConfigurationBuilder
enables a clean separation between configuration and logic while supporting third-party or fluent-style Java objects.
2.6 Unit Test
To ensure that the @ConfigurationBuilder
annotation correctly binds properties from the configuration file into builder-style Java classes, we can write a simple unit test using JUnit 5.
In this test case, we simulate the values from application.yml
using an in-memory configuration map and run a lightweight Micronaut ApplicationContext
in test mode. This allows us to verify that the MyConfig
class and its nested AppConfig
instance are properly initialized.
The test validates:
- String and numeric property binding (
app.name
,app.retries
) - Builder-style binding using
@ConfigurationBuilder
forapp.pool.*
- That the correct values are injected into
AppConfig
via its setter-style methods
package example; import io.micronaut.context.ApplicationContext; import io.micronaut.context.env.Environment; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; import java.util.Map; class MyConfigTest { @Test void testConfigurationBinding() { // Mock configuration values (same as those in application.yml) Map<String, Object> config = Map.of( "app.name", "MicronautApp", "app.retries", 5, "app.pool.max-connections", 10, "app.pool.min-connections", 2 ); try (ApplicationContext context = ApplicationContext.run(config, Environment.TEST)) { MyConfig myConfig = context.getBean(MyConfig.class); assertNotNull(myConfig); assertEquals("MicronautApp", myConfig.getName()); assertEquals(5, myConfig.getRetries()); AppConfig appConfig = myConfig.getAppConfig(); assertNotNull(appConfig); assertEquals(10, appConfig.getMaxConnections()); assertEquals(2, appConfig.getMinConnections()); } } }
If everything goes well, the test will pass successfully.
3. Conclusion
The @ConfigurationBuilder
annotation in Micronaut simplifies the task of binding structured configuration to builder-style Java classes. It’s especially handy when working with existing libraries or third-party builders. As demonstrated, it allows for a clean separation of configuration logic and provides a type-safe way to use configuration in your Micronaut applications. By combining @ConfigurationProperties
and @ConfigurationBuilder
, developers can take full advantage of Micronaut’s powerful configuration binding features.