-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Description
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:
-
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. -
Library configuration files should be loaded optionally and with transparency.
If a library provides its ownapplication.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
-
When multiple
application.ymlor.yamlfiles 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
-
Ideally, Micronaut would support a configurable loading strategy, so developers can explicitly opt into:
FIRST_MATCH(current behavior)MERGE_ALLFAIL_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
appmodule that definesapplication.yaml - A
librarymodule that defines its ownapplication.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>-
Build the Project:
./gradlew build
-
Run the Application:
./gradlew :app:run
-
Observe the Output:
Notice that the configuration value from
app/src/main/resources/application.yamlis not printed.
Instead, the value from
utilities/src/main/resources/application.ymlis used. -
Modify the Project:
Open
app/build.gradleand comment out the following line:implementation(project(":utilities")) -
Rebuild and Rerun the Application:
./gradlew build ./gradlew :app:run
-
Observe the Output Again:
Now, the configuration value from
app/src/main/resources/application.yamlis 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
Type
Projects
Status