Skip to content

application.yml from Libraries Can Override or Be Silently Ignored — No Merging or Warning #11703

@tmellanxt

Description

@tmellanxt

Expected Behavior

In this scenario, where a Micronaut application defines application.yaml and a library dependency provides its own application.yml, the expected behavior is:

  1. The application's configuration (application.yaml) should always take precedence.
    This aligns with developer expectations — the application's own config should never be overridden silently by a transitive library dependency.

  2. Library configuration files should be loaded optionally and with transparency.
    If a library provides its own application.yml, it may contain useful default values. These should be:

    • Merged after the application config (lower precedence)
    • Only if key conflicts don't exist
    • Accompanied by a log message indicating their source and loading behavior
  3. When multiple application.yml or .yaml files are found, Micronaut should:

    • Merge them (with deterministic ordering and documented rules), or
    • Emit a warning stating that only one was used, and which one
  4. Ideally, Micronaut would support a configurable loading strategy, so developers can explicitly opt into:

    • FIRST_MATCH (current behavior)
    • MERGE_ALL
    • FAIL_ON_DUPLICATE
    • Possibly others (e.g., APP_ONLY, APP_OVERRIDES_LIBS)

This approach ensures application authors retain full control over configuration while allowing library authors to provide sensible defaults without risk of overriding or being silently ignored.

Actual Behaviour

We created a reproducible Gradle multi-module Micronaut project with:

  • A main app module that defines application.yaml
  • A library module that defines its own application.yml

Expected: The application's config (application.yaml) should be loaded and take precedence.
Actual: The application's config is silently ignored — only the library's application.yml is used.

There are no logs, warnings, or exceptions indicating this override has occurred.


🔬 Why This Happens: Deep Dive into Micronaut’s YamlPropertySourceLoader

The root cause lies in the YamlPropertySourceLoader, which extends AbstractPropertySourceLoader. Here’s the key method in AbstractPropertySourceLoader:

private Optional<PropertySource> load(ResourceLoader resourceLoader, String fileName, int order) {
    if (this.isEnabled()) {
        Set<String> extensions = this.getExtensions(); // e.g., ["yml", "yaml"]
        Iterator<String> var5 = extensions.iterator();

        while (var5.hasNext()) {
            String ext = var5.next();
            String fileExt = fileName + "." + ext; // Tries application.yml then application.yaml

            Map<String, Object> finalMap = this.loadProperties(resourceLoader, fileName, fileExt);
            if (!finalMap.isEmpty()) {
                return Optional.of(this.createPropertySource(fileName, finalMap, order)); // First match wins
            }
        }
    }

    return Optional.empty();
}

### Steps To Reproduce


> **Prerequisite:** Ensure Java 21 is installed and configured as the active JDK.

1. **Clone the Repository:**
   ```bash
   git clone <repository-url>
   cd <repository-directory>
  1. Build the Project:

    ./gradlew build
  2. Run the Application:

    ./gradlew :app:run
  3. Observe the Output:

    Notice that the configuration value from
    app/src/main/resources/application.yaml is not printed.
    Instead, the value from
    utilities/src/main/resources/application.yml is used.

  4. Modify the Project:

    Open app/build.gradle and comment out the following line:

    implementation(project(":utilities"))
  5. Rebuild and Rerun the Application:

    ./gradlew build
    ./gradlew :app:run
  6. Observe the Output Again:

    Now, the configuration value from
    app/src/main/resources/application.yaml is printed as expected.


These steps demonstrate the issue where the application.yml from the utilities module overrides the application.yaml from the app module.

Environment Information

MacOS: 15.3.2 (24D81)
Java 21
Built tool: gradle

Example Application

https://github.com/tmellanxt/micronaut-yaml-bug

Version

4.7.6

🙋‍♂️ Contributor Statement

I am happy to contribute a fix for this issue and would appreciate guidance from the Micronaut team on the preferred direction. Specifically:

  • I am in favor of introducing a configurable loading strategy, such as:
    • micronaut.config.load-strategy=FIRST_MATCH | MERGE_ALL | FAIL_ON_DUPLICATE, etc.
  • I’d be glad to prototype a solution, update documentation, and write tests — but would like confirmation on which strategy or direction aligns with project goals before submitting a PR.

Additionally, it’s worth noting that this issue affects not just applications being overridden by libraries, but also cases where multiple libraries provide their own application.yml. In those cases, only one is loaded, and the rest are silently ignored, which can also break expected behavior unless libraries explicitly configure environment profiles or load configs differently.

Metadata

Metadata

Assignees

Labels

status: pr submittedA pull request has been submitted for the issuestatus: under considerationThe issue is being considered, but has not been accepted yettype: enhancementNew feature or request

Type

No type

Projects

Status

In progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions