Task List
Steps to Reproduce
- Create a Java record annotated with
@EachProperty where the @Parameter-annotated field is not alphabetically first among the record components:
@EachProperty("demos")
public record UnorderedConfiguration(
@Parameter String name, // 'name' sorts AFTER 'enabled' alphabetically
Mode mode,
boolean enabled) {
}
- Compile the project with the JDT (Eclipse) compiler (e.g. build from Eclipse or use
ecj as the annotation processor host).
- Run the application with configuration bound to
demos.*.
Expected Behaviour
All constructor parameters receive the correct injection annotation:
name → @Parameter (the @EachProperty key)
mode and enabled → @Property(name="demos.*.mode") / @Property(name="demos.*.enabled")
The bean is created and all fields are populated correctly.
Actual Behaviour
ConfigurationMetadataWriterVisitor.applyConfigurationInjectionIfNecessary zips getBeanProperties() with the constructor parameter array by position. Under JDT, TypeElement.getEnclosedElements() returns record accessor methods in alphabetical order rather than declaration order, so getBeanProperties() returns [enabled, mode, name] (alphabetical) while constructor.getParameters() returns [name, mode, enabled] (declaration order).
The positional zip therefore pairs:
| index |
beanProperties.get(i) (JDT) |
parameters[i] |
result |
| 0 |
enabled (no @Parameter) |
name |
name incorrectly gets @Property added |
| 1 |
mode (no @Parameter) |
mode |
correct |
| 2 |
name (has @Parameter) |
enabled |
enabled is silently skipped, never bound |
The bean either fails to load or enabled is always its default value (false).
The same code compiles and runs correctly under javac because getEnclosedElements() returns elements in declaration order there.
Root Cause
ConfigurationMetadataWriterVisitor.java, method applyConfigurationInjectionIfNecessary, lines 283–288 (5.1.x branch):
// BUG: positional lookup — breaks when compilers differ in getEnclosedElements() order
for (int i = 0; i < parameters.length; i++) {
ParameterElement parameter = parameters[i];
final PropertyElement bp = beanProperties.get(i);
if (CONSTRUCTOR_PARAMETERS_INJECTION_ANN.stream().noneMatch(bp::hasStereotype)) {
processConfigurationInjectParameter(...);
}
}
Fix: look up the matching property by name instead of by index (same approach already used in BeanIntrospectionWriter).
Environment Information
- Operating System: any (bug is compiler-dependent, not OS-dependent)
- Micronaut Version: 4.10.13 (confirmed); likely all versions
- JDK Version: 21 (confirmed); likely all versions with record support
Example Application
ordered.zip
Task List
Steps to Reproduce
@EachPropertywhere the@Parameter-annotated field is not alphabetically first among the record components:ecjas the annotation processor host).demos.*.Expected Behaviour
All constructor parameters receive the correct injection annotation:
name→@Parameter(the@EachPropertykey)modeandenabled→@Property(name="demos.*.mode")/@Property(name="demos.*.enabled")The bean is created and all fields are populated correctly.
Actual Behaviour
ConfigurationMetadataWriterVisitor.applyConfigurationInjectionIfNecessaryzipsgetBeanProperties()with the constructor parameter array by position. Under JDT,TypeElement.getEnclosedElements()returns record accessor methods in alphabetical order rather than declaration order, sogetBeanProperties()returns[enabled, mode, name](alphabetical) whileconstructor.getParameters()returns[name, mode, enabled](declaration order).The positional zip therefore pairs:
beanProperties.get(i)(JDT)parameters[i]enabled(no@Parameter)namenameincorrectly gets@Propertyaddedmode(no@Parameter)modename(has@Parameter)enabledenabledis silently skipped, never boundThe bean either fails to load or
enabledis always its default value (false).The same code compiles and runs correctly under javac because
getEnclosedElements()returns elements in declaration order there.Root Cause
ConfigurationMetadataWriterVisitor.java, methodapplyConfigurationInjectionIfNecessary, lines 283–288 (5.1.x branch):Fix: look up the matching property by name instead of by index (same approach already used in
BeanIntrospectionWriter).Environment Information
Example Application
ordered.zip