Skip to content

Admit nullable-valued maps in MergedAnnotation.asMap - #37450

Open
vlsi wants to merge 1 commit into
spring-projects:mainfrom
vlsi:asmap-nullable-bound
Open

vlsi wants to merge 1 commit into
spring-projects:mainfrom
vlsi:asmap-nullable-bound

Conversation

@vlsi

@vlsi vlsi commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

MergedAnnotation.asMap(Function, Adapt...) bounds T by Map<String, Object>, but AnnotationAttributes extends LinkedHashMap<String, @Nullable Object>. NullAway with full generic type inference (uber/NullAway#1585) therefore rejects the three spring-core calls that pass an AnnotationAttributes factory (AbstractMergedAnnotation.java:192, AnnotationUtils.java:884, :930): "AnnotationAttributes cannot be converted to Map<String, Object>".

The bound becomes Map<String, ? super Object>. For javac it is the same bound as before. Under JSpecify it admits both Map<String, Object> and Map<String, @Nullable Object>. Map<String, @Nullable Object> was rejected: with it, the same NullAway build reports "LinkedHashMap<String, Object> cannot be converted to Map<String, @Nullable Object>" for a caller's Map<String, Object> factory.

Verified with a NullAway snapshot built from subhramit/NullAway@3ed1de8a9: the three errors are gone, and a probe call with a LinkedHashMap<String, Object> factory compiles. ./gradlew :spring-core:check passes with the current NullAway 0.14.0. No test is added: the change only affects nullness annotations, which are not visible at runtime.

MergedAnnotation.asMap(Function, Adapt...) bounds its type variable by
Map<String, Object>, yet three call sites in spring-core pass factories
that create AnnotationAttributes, which extends LinkedHashMap<String,
@nullable Object>. Under JSpecify that type is outside the bound, and
NullAway with full generic type inference (uber/NullAway#1585) reports
it in AbstractMergedAnnotation.asAnnotationAttributes() and in
AnnotationUtils.getAnnotationAttributes() and computeDefaultValues():

    error: [NullAway] incompatible types: AnnotationAttributes cannot be converted to Map<String, Object> (AnnotationAttributes is a subtype of Map<String, @nullable Object>)

Declare the bound as Map<String, ? super Object> in MergedAnnotation and
its implementations. For javac this admits exactly what the old bound
did, while under JSpecify it admits both Map<String, Object> and
Map<String, @nullable Object>, and asMap can still put attribute values
into the map. Map<String, @nullable Object> would be the obvious bound,
but it rejects factories of Map<String, Object>: the same NullAway build
reports "LinkedHashMap<String, Object> cannot be converted to
Map<String, @nullable Object>" for such a factory.

The factory-less asMap(Adapt...) keeps returning Map<String, Object>:
the map it builds never holds null, since asMap skips attributes whose
value is null.

Signed-off-by: Vladimir Sitnikov <sitnikov.vladimir@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Oct 11, 2026
@sbrannen sbrannen added the in: core Issues in core modules (aop, beans, core, context, expression) label Oct 11, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

in: core Issues in core modules (aop, beans, core, context, expression) status: waiting-for-triage An issue we've not yet triaged or decided on

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants