Skip to content

Support for Spring's @Value annotation#1505

Merged
msridhar merged 14 commits intomasterfrom
value-annotation
Apr 8, 2026
Merged

Support for Spring's @Value annotation#1505
msridhar merged 14 commits intomasterfrom
value-annotation

Conversation

@msridhar
Copy link
Copy Markdown
Collaborator

@msridhar msridhar commented Mar 29, 2026

Fixes #1483

We add some specialized logic to treat Spring's @Value as an external initializer annotation, but only when its SpEL expression does not contain null (which would indicate the field could be initialized to null). Rather than adding a whole extra Handler for this, we bake in a very small amount of Spring-specific logic. Also do some refactorings and cleanup in NullabilityUtil while editing that code.

Summary by CodeRabbit

  • New Features

    • Added support for Spring @Value annotation in null-safety analysis, allowing proper handling of Spring-managed fields.
  • Documentation

    • Updated contribution guidelines with coding style recommendations for local variable annotations.
  • Tests

    • Added test coverage for Spring annotation field handling.

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 29, 2026

Codecov Report

❌ Patch coverage is 90.90909% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.47%. Comparing base (cf60f58) to head (2a3daac).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
...c/main/java/com/uber/nullaway/NullabilityUtil.java 84.61% 0 Missing and 2 partials ⚠️
...java/com/uber/nullaway/handlers/SpringHandler.java 87.50% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #1505      +/-   ##
============================================
+ Coverage     88.45%   88.47%   +0.02%     
- Complexity     2823     2837      +14     
============================================
  Files           100      101       +1     
  Lines          9446     9460      +14     
  Branches       1893     1895       +2     
============================================
+ Hits           8355     8370      +15     
+ Misses          530      529       -1     
  Partials        561      561              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 29, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a64104ad-1533-4067-9ee5-dd8e5937c3ff

📥 Commits

Reviewing files that changed from the base of the PR and between 523fae3 and 2a3daac.

📒 Files selected for processing (5)
  • nullaway/src/main/java/com/uber/nullaway/NullAway.java
  • nullaway/src/main/java/com/uber/nullaway/handlers/CompositeHandler.java
  • nullaway/src/main/java/com/uber/nullaway/handlers/Handler.java
  • nullaway/src/main/java/com/uber/nullaway/handlers/Handlers.java
  • nullaway/src/main/java/com/uber/nullaway/handlers/SpringHandler.java

Walkthrough

This PR adds support for Spring framework's @Value annotation to NullAway's default handler chain. The changes include: refactoring NullabilityUtil to generalize annotation lookup via findAnnotation to accept any Symbol and extracting a shared getAnnotationValue(AnnotationMirror) helper; introducing a new SpringHandler that skips field initialization checks for @Value-annotated fields when the annotation value contains SpEL null expressions; updating the Handler interface with a new shouldSkipFieldInitializationCheck hook method; modifying NullAway.java to defer to handlers via this new method; and adding a test case for Spring @Value field handling.

Possibly related PRs

  • PR #1295: Modifies annotation lookup and value-extraction logic in NullabilityUtil, sharing similar refactoring patterns for findAnnotation and getAnnotationValue method signatures.

Suggested reviewers

  • lazaroclapp
  • yuxincs
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Support for Spring's @Value annotation' accurately summarizes the main change: adding support for Spring's @Value annotation as an initializer.
Linked Issues check ✅ Passed The PR successfully implements the objective from issue #1483: adding @Value to the list of default initializers by treating @Value-annotated fields as externally initialized (unless SpEL contains null).
Out of Scope Changes check ✅ Passed All changes are scoped to implementing @Value support: refactoring annotation utilities, adding SpringHandler, updating Handler interface, and adding tests. No unrelated changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch value-annotation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@nullaway/src/main/java/com/uber/nullaway/NullAway.java`:
- Around line 2645-2648: The current logic calls
config.isExcludedFieldAnnotation(annotationName) first which short-circuits and
prevents SpringUtils.isInjectedByValueAnnotation(fieldSymbol, annotationName)
from being evaluated for org.springframework.beans.factory.annotation.Value;
change the precedence so SpringUtils.isInjectedByValueAnnotation is evaluated
first and if it returns true return true, otherwise fall back to
config.isExcludedFieldAnnotation(annotationName) to decide; update the block
that currently calls config.isExcludedFieldAnnotation(...) then
SpringUtils.isInjectedByValueAnnotation(...) to call
SpringUtils.isInjectedByValueAnnotation(...) first (or explicitly allow the
Value annotation through) so `@Value`("#{null}") is handled by the Spring-specific
check.

In `@nullaway/src/main/java/com/uber/nullaway/SpringUtils.java`:
- Around line 47-48: Remove the local `@Nullable` annotation on the variable
declaration for annotationValue; change "@Nullable String annotationValue =
NullabilityUtil.getAnnotationValue(fieldSymbol, VALUE_ANNOT, true);" to just
"String annotationValue = NullabilityUtil.getAnnotationValue(fieldSymbol,
VALUE_ANNOT, true);" and also remove any now-unused Nullable import. Keep the
call to NullabilityUtil.getAnnotationValue and references to fieldSymbol and
VALUE_ANNOT unchanged.
- Around line 34-35: VALUE_NULL_SPEL_PATTERN in SpringUtils.java currently
matches "null" inside SpEL even when it's inside quoted string literals; update
the pattern so it skips over single-quoted and double-quoted substrings when
scanning inside "#{...}" (i.e., match a repeated sequence of either
non-quote/non-brace characters OR a single-quoted string OR a double-quoted
string, and look for a word-boundary "null" only in those non-quoted parts),
replace the existing Pattern.compile usage that defines VALUE_NULL_SPEL_PATTERN
with this refined pattern, and add a unit test that verifies `@Value`("#{'null'}")
(and similarly quoted "null") is not flagged as nullable while an unquoted null
literal is flagged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7af2b912-a73b-443c-a8f2-972b3ddacd0c

📥 Commits

Reviewing files that changed from the base of the PR and between e78dc89 and 933dace.

📒 Files selected for processing (4)
  • nullaway/src/main/java/com/uber/nullaway/NullAway.java
  • nullaway/src/main/java/com/uber/nullaway/NullabilityUtil.java
  • nullaway/src/main/java/com/uber/nullaway/SpringUtils.java
  • nullaway/src/test/java/com/uber/nullaway/FrameworkTests.java

Comment on lines +2645 to +2648
if (config.isExcludedFieldAnnotation(annotationName)) {
return true;
}
return SpringUtils.isInjectedByValueAnnotation(fieldSymbol, annotationName);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t let ExcludedFieldAnnotations short-circuit the @Value analysis.

Lines 2645-2648 return early for any excluded field annotation, so a config that includes org.springframework.beans.factory.annotation.Value will never reach the new Spring-specific null-SpEL check. In that setup, @Value("#{null}") is still treated as always initialized and the false negative this PR is trying to fix remains.

Suggested precedence change
         .anyMatch(
             anno -> {
               String annotationName = anno.getAnnotationType().toString();
+              if (annotationName.equals(SpringUtils.VALUE_ANNOT)) {
+                return SpringUtils.isInjectedByValueAnnotation(fieldSymbol, annotationName);
+              }
               if (config.isExcludedFieldAnnotation(annotationName)) {
                 return true;
               }
-              return SpringUtils.isInjectedByValueAnnotation(fieldSymbol, annotationName);
+              return false;
             });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (config.isExcludedFieldAnnotation(annotationName)) {
return true;
}
return SpringUtils.isInjectedByValueAnnotation(fieldSymbol, annotationName);
String annotationName = anno.getAnnotationType().toString();
if (annotationName.equals(SpringUtils.VALUE_ANNOT)) {
return SpringUtils.isInjectedByValueAnnotation(fieldSymbol, annotationName);
}
if (config.isExcludedFieldAnnotation(annotationName)) {
return true;
}
return false;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nullaway/src/main/java/com/uber/nullaway/NullAway.java` around lines 2645 -
2648, The current logic calls config.isExcludedFieldAnnotation(annotationName)
first which short-circuits and prevents
SpringUtils.isInjectedByValueAnnotation(fieldSymbol, annotationName) from being
evaluated for org.springframework.beans.factory.annotation.Value; change the
precedence so SpringUtils.isInjectedByValueAnnotation is evaluated first and if
it returns true return true, otherwise fall back to
config.isExcludedFieldAnnotation(annotationName) to decide; update the block
that currently calls config.isExcludedFieldAnnotation(...) then
SpringUtils.isInjectedByValueAnnotation(...) to call
SpringUtils.isInjectedByValueAnnotation(...) first (or explicitly allow the
Value annotation through) so `@Value`("#{null}") is handled by the Spring-specific
check.

Comment thread nullaway/src/main/java/com/uber/nullaway/SpringUtils.java Outdated
Comment thread nullaway/src/main/java/com/uber/nullaway/SpringUtils.java Outdated
@msridhar msridhar marked this pull request as draft March 29, 2026 16:09
@msridhar msridhar marked this pull request as ready for review March 29, 2026 16:47
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
nullaway/src/main/java/com/uber/nullaway/NullAway.java (1)

2645-2649: ⚠️ Potential issue | 🟠 Major

Handle @Value before excluded-annotation short-circuit.

At Line 2645, excluded annotations return early before the Spring @Value null-expression check runs. If org.springframework.beans.factory.annotation.Value is configured as an excluded field annotation, @Value("#{null}") is treated as initialized and the null-producing case is missed.

Suggested precedence fix
     return NullabilityUtil.getAllAnnotations(fieldSymbol, config)
         .anyMatch(
             anno -> {
               String annotationName = anno.getAnnotationType().toString();
+              if (annotationName.equals(SpringUtils.VALUE_ANNOT)) {
+                return SpringUtils.isInjectedByValueAnnotation(anno);
+              }
               if (config.isExcludedFieldAnnotation(annotationName)) {
                 return true;
               }
-              return SpringUtils.isInjectedByValueAnnotation(anno);
+              return false;
             });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nullaway/src/main/java/com/uber/nullaway/NullAway.java` around lines 2645 -
2649, The exclusion check short-circuits before recognizing Spring `@Value`
null-expressions: change the logic in the predicate so
SpringUtils.isInjectedByValueAnnotation(anno) is evaluated before or in
conjunction with config.isExcludedFieldAnnotation(annotationName) (e.g., call
isInjectedByValueAnnotation first and if true return its result, otherwise apply
isExcludedFieldAnnotation) so that `@Value`("#{null}") is not treated as
initialized when the annotation is configured as excluded; update the predicate
surrounding config.isExcludedFieldAnnotation and
SpringUtils.isInjectedByValueAnnotation to enforce this precedence.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@nullaway/src/main/java/com/uber/nullaway/NullAway.java`:
- Around line 2645-2649: The exclusion check short-circuits before recognizing
Spring `@Value` null-expressions: change the logic in the predicate so
SpringUtils.isInjectedByValueAnnotation(anno) is evaluated before or in
conjunction with config.isExcludedFieldAnnotation(annotationName) (e.g., call
isInjectedByValueAnnotation first and if true return its result, otherwise apply
isExcludedFieldAnnotation) so that `@Value`("#{null}") is not treated as
initialized when the annotation is configured as excluded; update the predicate
surrounding config.isExcludedFieldAnnotation and
SpringUtils.isInjectedByValueAnnotation to enforce this precedence.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: b33608b7-9445-4099-a499-03662d62d978

📥 Commits

Reviewing files that changed from the base of the PR and between 933dace and 523fae3.

📒 Files selected for processing (5)
  • AGENTS.md
  • nullaway/src/main/java/com/uber/nullaway/NullAway.java
  • nullaway/src/main/java/com/uber/nullaway/NullabilityUtil.java
  • nullaway/src/main/java/com/uber/nullaway/SpringUtils.java
  • nullaway/src/test/java/com/uber/nullaway/FrameworkTests.java

@msridhar msridhar requested a review from lazaroclapp March 29, 2026 17:52
Copy link
Copy Markdown
Collaborator

@lazaroclapp lazaroclapp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, code itself LGTM, but I do want to question a bit why we don't want this in a handler. I think overall the evidence is that handlers are fine performance-wise, so the tradeoff is slightly more code for a cleaner separation of concerns.

I feel support for specific frameworks beyond JSpecify and common flavors of @Nullable annotations should almost always go in either library models or handlers. It can be a general SpringHandler, in case later we need more spring specific functionality.

You call in the end, happy to stamp this if you want, code seems fine :)

@msridhar
Copy link
Copy Markdown
Collaborator Author

msridhar commented Apr 8, 2026

@lazaroclapp you convinced me :-) I moved the logic to a new SpringHandler.

@msridhar msridhar requested a review from lazaroclapp April 8, 2026 00:10
Copy link
Copy Markdown
Collaborator

@lazaroclapp lazaroclapp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Appreciate it being a handler

}

@Override
public boolean shouldSkipFieldInitializationCheck(AnnotationMirror annotationMirror) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Surprised we didn't have something like this before, but at a glance, we don't, because we always supported the list of initialization skipping annotations "natively" in core + arguments. This could now be used to extend library models to add initialization suppressing annotations 🤔 (This is a nice to have, though, not an urgent task, and much less a blocker for this PR, just musing)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, in all cases thus far, just having the fully-qualified annotation name was enough, so doing it as a CLI argument worked fine. This case was different, since we don't just care about the name of the annotation, but also the contents of its value attribute. I think I'd like to see more motivation before adding general support in library models for this

@msridhar msridhar merged commit 498636f into master Apr 8, 2026
12 checks passed
@msridhar msridhar deleted the value-annotation branch April 8, 2026 19:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add @Value to the list of default initializers

2 participants