Skip to content

JDT compiler: @EachProperty record with @Parameter field not alphabetically first silently loses property binding #12658

Description

@jochenseeber

Task List

  • Steps to reproduce provided
  • Stacktrace (if present) provided
  • Example that reproduces the problem uploaded to GitHub
  • Full description of the issue provided (see below)

Steps to Reproduce

  1. 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) {
}
  1. Compile the project with the JDT (Eclipse) compiler (e.g. build from Eclipse or use ecj as the annotation processor host).
  2. 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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions