Software Development

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() or withXyz(). Use the methods or prefix 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 for app.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.

Yatin Batra

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button