Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
tmellanxt opened this issue Mar 31, 2025 · 4 comments
Labels
status: under consideration The issue is being considered, but has not been accepted yet type: enhancement New feature or request

Comments

@tmellanxt
Copy link

tmellanxt commented Mar 31, 2025

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.

@graemerocher
Copy link
Contributor

A configurable loading strategy seems reasonable. We cannot include breaking changes to the default behaviour.

@dstepanov
Copy link
Contributor

The solution here would be add a special classpath*: config location like it's in Spring and you can override the default one Arrays.asList("classpath:/", "file:config/")

@graemerocher
Copy link
Contributor

not sure if that is enough since it then depends on class path order which is loaded first

@dstepanov
Copy link
Contributor

I think you need to scan jars in that case, so it's slower but allows to load all the duplicates. My idea is not to have it by default but let the user override the default config location strategy.

@graemerocher graemerocher added type: enhancement New feature or request status: under consideration The issue is being considered, but has not been accepted yet labels Apr 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: under consideration The issue is being considered, but has not been accepted yet type: enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants