Skip to content

Conversation

@msridhar
Copy link
Collaborator

@msridhar msridhar commented Sep 3, 2025

This lets us take advantage of all the latest javac fixes, including proper support for reading type use annotations from bytecodes. Previously we were compiling using JDK 17.

The key changes are in TypeMetadataBuilder. Before, it invoked javac APIs directly on JDK 17 or earlier, and used reflection for JDK 21+. Now we switch it and use reflection on the earlier JDKs, invoking APIs directly on JDK 21+. This makes sense since for JSpecify mode we strongly encourage users to compile with a recent JDK. Other changes are minor tweaks for compatibility with the new compiler version.

Summary by CodeRabbit

  • New Features

    • Broader Java compatibility, including support for JDK 21+ and newer toolchains.
  • Refactor

    • Reworked internal type metadata handling for consistent behavior across JDK versions without changing public APIs.
  • Chores

    • Updated build to compile against the latest available JDK.
    • Tuned Android-related build options to reduce Java deprecation warnings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 3, 2025

Walkthrough

Updates Gradle configurations to adjust Java versions/toolchains across modules and adds Android compileOptions for Java 11. Refactors TypeMetadataBuilder to support multiple JDKs by introducing a runtime-selected implementation: reflection-based for JDK ≤17 and direct API usage for JDK 21+, adding a new JDK21TypeMetadataBuilder class.

Changes

Cohort / File(s) Summary
Root build toolchain update
build.gradle
Changes Gradle toolchain languageVersion from 17 to 24 and updates comment to use the latest available JDK.
JDK javac plugin build adjustments
jdk-javac-plugin/build.gradle
Removes enforced JDK 21 toolchain; configures each JavaCompile task with source/target Java 17; retains existing --add-exports options.
Android test module Java config
jar-infer/test-android-lib-jarinfer/build.gradle
Adds android.compileOptions with sourceCompatibility/targetCompatibility set to Java 11 to avoid JDK 24 deprecation warnings for 8.
Type metadata JDK compatibility
nullaway/src/main/java/com/uber/nullaway/generics/TypeMetadataBuilder.java
Introduces runtime-dispatch between reflection-based builder for JDK ≤17 and direct API builder for JDK 21+; adds private MethodHandle utilities; consolidates metadata construction/clone paths; adds public nested class JDK21TypeMetadataBuilder.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant TMB as TypeMetadataBuilder
  participant RT as Runtime JDK Detector
  participant JB21 as JDK21TypeMetadataBuilder (direct)
  participant JB17 as JDK17AndEarlierTypeMetadataBuilder (reflection)
  participant JDK as JDK Types/APIs

  Client->>TMB: buildTypeMetadata(type)
  TMB->>RT: detect JDK version
  alt JDK >= 21
    TMB->>JB21: build(type)
    JB21->>JDK: Use public Type APIs (construct/get/add metadata)
    JDK-->>JB21: TypeMetadata
    JB21-->>TMB: TypeMetadata
  else JDK <= 17
    TMB->>JB17: build(type)
    JB17->>JDK: Lookup constructors/methods via MethodHandles
    JB17->>JDK: Invoke handles to construct/clone metadata
    JDK-->>JB17: TypeMetadata
    JB17-->>TMB: TypeMetadata
  end
  TMB-->>Client: TypeMetadata
  note over JB17,JDK: Reflection/MethodHandles path avoids direct refs to newer APIs
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • yuxincs
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch rewrite-for-reflection-on-older-jdks

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@codecov
Copy link

codecov bot commented Sep 3, 2025

Codecov Report

❌ Patch coverage is 88.88889% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.55%. Comparing base (88297f9) to head (3ed3014).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
...om/uber/nullaway/generics/TypeMetadataBuilder.java 88.88% 7 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff            @@
##             master    #1276   +/-   ##
=========================================
  Coverage     88.55%   88.55%           
  Complexity     2431     2431           
=========================================
  Files            91       91           
  Lines          8013     8022    +9     
  Branches       1598     1598           
=========================================
+ Hits           7096     7104    +8     
- Misses          458      459    +1     
  Partials        459      459           

☔ 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.

@msridhar msridhar marked this pull request as ready for review September 3, 2025 19:27
Copy link
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
build.gradle (1)

99-104: Exclude jdk-javac-plugin from Java 11 default to avoid downgrading it.

This project intentionally compiles with Java 17 features.

Apply:

 tasks.withType(JavaCompile) {
-    java.sourceCompatibility = JavaVersion.VERSION_11
-    java.targetCompatibility = JavaVersion.VERSION_11
+    // Preserve module-specific settings (e.g., :jdk-javac-plugin uses 17)
+    if (project.path != ":jdk-javac-plugin") {
+        java.sourceCompatibility = JavaVersion.VERSION_11
+        java.targetCompatibility = JavaVersion.VERSION_11
+    }
 }
🧹 Nitpick comments (2)
nullaway/src/main/java/com/uber/nullaway/generics/TypeMetadataBuilder.java (2)

172-181: Catching Throwable is broad.

Narrow to ReflectiveOperationException where possible to avoid masking serious errors.

Example:

-    } catch (Throwable e) {
+    } catch (ReflectiveOperationException e) {
       throw new RuntimeException(e);
     }

Note: keep Throwable where MethodHandle.invoke can throw linkage errors you must surface.


229-267: Tests: cover both paths (≤17 and ≥21) and all factory methods.

Per prior learning on NullAway JSpecify tests, prefer failing when unsupported rather than skipping; add matrix tests on JDK 11, 17, 21, 24 to exercise both builders.

We’re applying your earlier guidance from PR #1245 about failing tests over skipping in versioned scenarios.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 88297f9 and 3ed3014.

📒 Files selected for processing (4)
  • build.gradle (1 hunks)
  • jar-infer/test-android-lib-jarinfer/build.gradle (1 hunks)
  • jdk-javac-plugin/build.gradle (1 hunks)
  • nullaway/src/main/java/com/uber/nullaway/generics/TypeMetadataBuilder.java (5 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: msridhar
PR: uber/NullAway#1245
File: guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java:101-102
Timestamp: 2025-08-14T18:50:06.159Z
Learning: In NullAway JSpecify tests, when JDK version requirements exist due to bytecode annotation reading capabilities, prefer failing tests over skipping them on unsupported versions to ensure CI catches regressions and enforces proper JDK version usage for developers.
📚 Learning: 2025-08-14T18:50:06.159Z
Learnt from: msridhar
PR: uber/NullAway#1245
File: guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java:101-102
Timestamp: 2025-08-14T18:50:06.159Z
Learning: In NullAway JSpecify tests, when JDK version requirements exist due to bytecode annotation reading capabilities, prefer failing tests over skipping them on unsupported versions to ensure CI catches regressions and enforces proper JDK version usage for developers.

Applied to files:

  • jdk-javac-plugin/build.gradle
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Build and test on ubuntu-latest
  • GitHub Check: Build spring-framework with snapshot
  • GitHub Check: Build and test on windows-latest
  • GitHub Check: Build caffeine with snapshot
  • GitHub Check: Build and test on macos-latest
🔇 Additional comments (6)
jar-infer/test-android-lib-jarinfer/build.gradle (1)

27-31: Good: pin Android module to Java 11.

This avoids -source/-target 8 warnings under JDK 24 and is consistent with AGP’s supported toolchains.

jdk-javac-plugin/build.gradle (1)

8-12: Remove Java 11 override concern: root’s tasks.withType(JavaCompile) block is at the root project level (lines 101–104) and isn’t inside the subprojects block, so it doesn’t affect this module’s Java 17 settings.

Likely an incorrect or invalid review comment.

build.gradle (1)

59-65: Confirm JDK 24 provisioning and Error Prone compatibility on CI. I searched for all javaCompiler, toolchain, and compatibility settings—aside from the intentional Java 11 source/target fallbacks and module-specific toolchains, there are no unintended overrides—so please verify that your CI runners can provision JDK 24 (via the foogJ resolver plugin) and that Error Prone + --add-exports flags work as expected.

nullaway/src/main/java/com/uber/nullaway/generics/TypeMetadataBuilder.java (3)

242-247: OK: clone for JDK 21+ without cloneWithMetadata.

dropMetadata/addMetadata matches 21+ API.


249-266: Constructors for 21+ look correct.

Using getMetadata() list and new ClassType/ArrayType/WildcardType signatures aligns with JDK 21+.


41-50: Lazy initialization is fine; avoid early linkage on newer JDKs.

Static handles are confined to the <=17 builder class; it won’t initialize when running on 21+. Good.

Comment on lines +61 to 64
// Always compile using the latest JDK we can
javaCompiler = javaToolchains.compilerFor {
languageVersion = JavaLanguageVersion.of(17)
languageVersion = JavaLanguageVersion.of(24)
}
Copy link
Contributor

@coderabbitai coderabbitai bot Sep 3, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Don’t force JDK 24 toolchain on Android modules.

AGP may not run/compile with a JDK 24 toolchain. Limit the toolchain assignment to non-Android projects.

Apply:

 tasks.withType(JavaCompile) {
-    // Always compile using the latest JDK we can
-    javaCompiler = javaToolchains.compilerFor {
-        languageVersion = JavaLanguageVersion.of(24)
-    }
+    // Always compile using the latest JDK we can (exclude Android modules)
+    if (!project.plugins.hasPlugin("com.android.application") &&
+        !project.plugins.hasPlugin("com.android.library")) {
+        javaCompiler = javaToolchains.compilerFor {
+            languageVersion = JavaLanguageVersion.of(24)
+        }
+    }
 }
📝 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
// Always compile using the latest JDK we can
javaCompiler = javaToolchains.compilerFor {
languageVersion = JavaLanguageVersion.of(17)
languageVersion = JavaLanguageVersion.of(24)
}
tasks.withType(JavaCompile) {
// Always compile using the latest JDK we can (exclude Android modules)
if (!project.plugins.hasPlugin("com.android.application") &&
!project.plugins.hasPlugin("com.android.library")) {
javaCompiler = javaToolchains.compilerFor {
languageVersion = JavaLanguageVersion.of(24)
}
}
}
🤖 Prompt for AI Agents
In build.gradle around lines 61 to 64, the project unconditionally sets
javaCompiler to JDK 24 which can break Android Gradle Plugin builds; guard the
toolchain assignment so it only applies to non-Android projects (e.g., check for
absence of Android application/library plugins or run only for JVM
modules/subprojects) and move the javaToolchains.compilerFor block inside that
conditional so Android modules keep their default or AGP-compatible JDK.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Works fine for now, I'm going to keep it

Copy link
Contributor

Choose a reason for hiding this comment

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

@msridhar Understood! I'll defer to your judgment on this. If any compatibility issues arise with the Android modules and JDK 24 toolchain in the future, you know where to find the suggested guard condition.


🧠 Learnings used
Learnt from: msridhar
PR: uber/NullAway#1245
File: guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java:101-102
Timestamp: 2025-08-14T18:50:06.159Z
Learning: In NullAway JSpecify tests, when JDK version requirements exist due to bytecode annotation reading capabilities, prefer failing tests over skipping them on unsupported versions to ensure CI catches regressions and enforces proper JDK version usage for developers.

Comment on lines +235 to +241
@Override
public TypeMetadata create(com.sun.tools.javac.util.List<Attribute.TypeCompound> attrs) {
ListBuffer<Attribute.TypeCompound> b = new ListBuffer<>();
b.appendList(attrs);
return new TypeMetadata.Annotations(b);
}

Copy link
Contributor

@coderabbitai coderabbitai bot Sep 3, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

Bug: wrong constructor argument type; pass List, not ListBuffer.

TypeMetadata.Annotations expects a com.sun.tools.javac.util.List; ListBuffer is not a subtype.

Apply:

-      ListBuffer<Attribute.TypeCompound> b = new ListBuffer<>();
-      b.appendList(attrs);
-      return new TypeMetadata.Annotations(b);
+      return new TypeMetadata.Annotations(attrs);
📝 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
@Override
public TypeMetadata create(com.sun.tools.javac.util.List<Attribute.TypeCompound> attrs) {
ListBuffer<Attribute.TypeCompound> b = new ListBuffer<>();
b.appendList(attrs);
return new TypeMetadata.Annotations(b);
}
@Override
public TypeMetadata create(com.sun.tools.javac.util.List<Attribute.TypeCompound> attrs) {
return new TypeMetadata.Annotations(attrs);
}
🤖 Prompt for AI Agents
In nullaway/src/main/java/com/uber/nullaway/generics/TypeMetadataBuilder.java
around lines 235 to 241, the code constructs a ListBuffer and passes it directly
to TypeMetadata.Annotations, but that constructor expects a
com.sun.tools.javac.util.List, not a ListBuffer; change the call to pass the
ListBuffer converted to a com.sun.tools.javac.util.List (e.g., call b.toList())
so the returned value is new TypeMetadata.Annotations(b.toList()) instead of
passing the ListBuffer itself.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The code compiles fine. This is for JDK 21+

Copy link
Contributor

Choose a reason for hiding this comment

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

, and the constructor pattern shows that ListBuffer objects are routinely used in contexts where they need to be converted to List objects.

In JDK 21+, the TypeMetadata.Annotations constructor likely accepts ListBuffer directly or ListBuffer implements the expected interface/extends the expected class that makes it compatible with the constructor parameter. Since the code compiles successfully as you mentioned, the direct passing of ListBuffer to the constructor is indeed valid.

I was overly cautious in my analysis and should have trusted that the code compiles correctly. The JDK 21+ changes to the internal APIs appear to have made this usage pattern valid.

🐰
/| /|
( ✌️ )


✏️ Learnings added
Learnt from: msridhar
PR: uber/NullAway#1276
File: nullaway/src/main/java/com/uber/nullaway/generics/TypeMetadataBuilder.java:235-241
Timestamp: 2025-09-03T19:40:11.062Z
Learning: In JDK 21+, ListBuffer can be passed directly to TypeMetadata.Annotations constructor without needing conversion to List via toList() method. The APIs were updated to make this compatible.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: msridhar
PR: uber/NullAway#1245
File: guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java:101-102
Timestamp: 2025-08-14T18:50:06.159Z
Learning: In NullAway JSpecify tests, when JDK version requirements exist due to bytecode annotation reading capabilities, prefer failing tests over skipping them on unsupported versions to ensure CI catches regressions and enforces proper JDK version usage for developers.

@msridhar msridhar requested a review from yuxincs September 3, 2025 19:43
@msridhar msridhar enabled auto-merge (squash) September 3, 2025 19:44
@msridhar msridhar merged commit 1f49bef into master Sep 4, 2025
9 of 11 checks passed
@msridhar msridhar deleted the rewrite-for-reflection-on-older-jdks branch September 4, 2025 13:22
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.

3 participants