From ca7de3b662e64c6e107870de536fe2b7fbfd314a Mon Sep 17 00:00:00 2001 From: Nicole Lee Date: Thu, 23 Jul 2026 19:47:00 +0000 Subject: [PATCH] feat: add ErrorProne and NullAway integration guide and JSpecify migration playbook --- docs/errorprone_nullaway_integration_guide.md | 167 ++++++++++++++++++ .../jspecify_automation_migration_playbook.md | 107 +++++++++++ 2 files changed, 274 insertions(+) create mode 100644 docs/errorprone_nullaway_integration_guide.md create mode 100644 docs/jspecify_automation_migration_playbook.md diff --git a/docs/errorprone_nullaway_integration_guide.md b/docs/errorprone_nullaway_integration_guide.md new file mode 100644 index 000000000000..bfb8c202c259 --- /dev/null +++ b/docs/errorprone_nullaway_integration_guide.md @@ -0,0 +1,167 @@ +# Guide for Integrating ErrorProne and NullAway +This guide outlines how to integrate ErrorProne and NullAway into your Java project. You can choose to implement basic ErrorProne analysis or extend it with NullAway for strict JSpecify nullability validation. + +## Definitions +* **ErrorProne**: A Java compiler plugin created by Google that hooks into the compilation process to identify common bug patterns at compile-time. +* **NullAway**: A fast, low-overhead null-pointer checker designed as an ErrorProne plugin. It eliminates NullPointerExceptions (NPEs) by checking annotations on method signatures and variable declarations. +* **JSpecify**: A standard set of annotations (like `@NullMarked` and `@Nullable`) that define the nullness contracts for Java types. NullAway has a strict JSpecifyMode that aligns its rules with the JSpecify spec. + +--- + +## 1. Basic ErrorProne Setup +ErrorProne hooks into the compilation process to identify bug patterns. This is configured directly inside the `maven-compiler-plugin`. + +### Maven Configuration +Add ErrorProne to the `` of the `maven-compiler-plugin`: +```xml + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + + + com.google.errorprone + error_prone_core + ${errorprone.version} + + + + +``` + +### Gradle Configuration +```groovy +plugins { + id("net.ltgt.errorprone") version "${errorprone.plugin.version}" +} +dependencies { + errorprone("com.google.errorprone:error_prone_core:${errorprone.version}") +} +``` + +--- + +## 2. Adding NullAway +NullAway runs as an annotation processor plug-in inside ErrorProne. To configure it: +1. Add NullAway to the compiler's annotation processor paths. +2. Pass flags enabling NullAway and setting packages to scan. + +### [Optional] Understanding NullAway JSpecify Mode +By default, NullAway checks nullability using legacy checking rules. Passing `-XepOpt:NullAway:JSpecifyMode=true` enables **JSpecify Mode**, which aligns NullAway's static analysis with the JSpecify 1.0 specifications: +* **Generics Nullability**: Enables checking annotations inside generic parameters (e.g. `List<@Nullable String>`). +* **Subclass Compatibility**: Enforces strict Liskov Substitution Principle compliance, ensuring subclasses do not accept fewer nulls or return more nulls than their parent class methods. +* **Array element checks**: Validates nullability contracts on array references and array type arguments. +* **Bytecode Symbol Dependency**: Checking generics and type-use nullability requires the compiler to preserve these type-use annotations in compiled bytecode symbols. Consequently, JSpecify Mode requires building on JDK 22+ or using OpenJDK 17/21 with the `-XDaddTypeAnnotationsToSymbol=true` compiler argument. + +### Maven NullAway Setup +```xml + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + true + + -XDcompilePolicy=simple + + -Xplugin:ErrorProne -Xep:NullAway:ERROR -XepOpt:NullAway:AnnotatedPackages= -XepOpt:NullAway:JSpecifyMode=true + -XDshould-stop.ifError=FLOW + -XDaddTypeAnnotationsToSymbol=true + + + -J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED + -J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED + -J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED + + + + com.google.errorprone + error_prone_core + ${errorprone.version} + + + com.uber.nullaway + nullaway + ${nullaway.version} + + + + +``` + +### Gradle NullAway Setup +```groovy +dependencies { + errorprone("com.uber.nullaway:nullaway:${nullaway.version}") +} +``` +Configure the compiler plugin to run ErrorProne as a compiler plugin and NullAway as an annotation processor. +* **Classpath Note**: Ensure `error_prone_api` is on the classpath to prevent runtime failures. +* **Recommendation for Build Severity**: If your project has many existing violations, default to `WARN` initially. Upgrade to `ERROR` to break the build only after those issues are resolved. + +--- + +## 3. Running On-Demand with Maven Profiles (Optional) +Because running deep nullness checks on every compile slows down build times, we encapsulate these checks into a Maven Profile to run them on-demand. + +### Profile A: Strict Check (`nullaway`) +This profile enforces NullAway checks on annotated packages and breaks the build on any violation: +```xml + + + nullaway + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + + + + + + + +``` + +--- + +## 4. Verification & Testing + +### Understanding NullAway Configuration Flags +* **`-Xep:NullAway:ERROR`** (Recommended Setting: `ERROR` / `WARN`): Set to `ERROR` to break the build on any nullability contract violation and try to resolve the flagged reports. If too many to be resolved, set to `WARN` to print warnings without failing the compilation. +* **`AnnotatedPackages`** (Recommended Setting: ``): Required. A comma-separated list of package prefixes that NullAway scans. Anything outside these package prefixes will be ignored. +* **`JSpecifyMode`** (Recommended Setting: `true`): Required for JSpecify. Enforces strict JSpecify rules, including type-use checks, array type-argument checks, and subclass compatibility constraints. + +### How to Verify Success +Run the compilation command: +```shell +mvn clean compile +``` +*(or `mvn clean compile -Pnullaway` if using the profile)* + +* **Success**: You see `BUILD SUCCESS`. This guarantees your code has no nullability violations within the annotated packages. +* **To verify the checks are actively running**: + 1. Add an intentional bug method (introduce a temporary violation to one of your Java files). + 2. Run the build with `mvn clean compile -X` and search the output logs for `-Xplugin:ErrorProne` or `NullAway`. If Maven is passing those arguments to the compiler, the checker is active. +* **Failure**: The compiler fails with clear error messages pointing to code lines. Ex: + `[ERROR] /path/to/File.java:[45,12] error: [NullAway] passing @Nullable parameter to a @NonNull method` + +--- + +## 5. Supported JDK Versions & Compiler Requirements +When `JSpecifyMode` is enabled, NullAway requires the compiler to support reading type-use annotations from bytecode symbols: +* **JDK 22 or higher**: Fully supported natively. +* **JDK 17 and 21**: You must pass `-XDaddTypeAnnotationsToSymbol=true` in `compilerArgs`. + * **Important**: This flag is only supported by OpenJDK builds (e.g. Temurin, Zulu) as of release 21.0.8 / 17.0.19. It is not supported by Oracle JDK. +* **NullAway Version**: Use **`0.11.0` or higher** on JDK 21+ to avoid compiler crashes due to a known `NullPointerException` inside the analyzer's generics checks (e.g., `castToNonNull failed!` in `GenericsChecks`). diff --git a/docs/jspecify_automation_migration_playbook.md b/docs/jspecify_automation_migration_playbook.md new file mode 100644 index 000000000000..4e015169228f --- /dev/null +++ b/docs/jspecify_automation_migration_playbook.md @@ -0,0 +1,107 @@ +# Automation & Migration Playbook +Onboarding a large codebase to strict JSpecify null safety requires a systematic approach. This document details the automation workflow and best practices. + +--- + +## 1. Generated vs. Handwritten Code Handling +Before starting the migration, identify which parts of the codebase are generated vs. handwritten: +* **Handwritten Modules (GAX, Auth, parent modules)**: Apply the 3-step automation workflow below directly to the source tree. +* **Generated Modules**: Do not edit generated classes manually! Instead, update the code generator engine (`gapic-generator-java`) to output JSpecify annotations (`@NullMarked` on classes/packages and `@Nullable` on generic/nullable return methods) during its code generation phase. + +--- + +## 2. The 3-Step Automation Workflow +Use these three parts in order to automate the mechanical tasks of JSpecify migration. + +### Part 1: Bulk `@NullMarked` Annotation (Automated Script) +Use a python script to search Java source directories and inject `@NullMarked` at the package or class level: +* **Package-level**: Add to `package-info.java` files. +* **Class-level**: Prepend `@NullMarked` to top-level classes, interfaces, or enums. + +*Example class-level injection pattern (Python)*: +```python +package_match = re.search(r"^(package\s+[\w\.]+;)", content, re.MULTILINE) +if package_match: + package_line = package_match.group(1) + content = content.replace(package_line, package_line + "\nimport org.jspecify.annotations.NullMarked;") +# Add @NullMarked before the class/interface/enum +content = re.sub(r"\n(public\s+)?(class|interface|enum)\s+", r"\n@NullMarked\n\2 ", content) +``` + +--- + +### Part 2: Legacy Javax Migration (Automated Script) +Translate legacy annotations (e.g. `javax.annotation.Nullable`) to JSpecify `@Nullable`. If annotations are in declaration positions, programmatically reposition them to type-use positions: +```python +# Reposition from declaration-use to type-use position if necessary +content = re.sub(r"@Nullable\s+public\s+(\w+)", r"public @Nullable \1", content) +# Replace imports +content = content.replace("import javax.annotation.Nullable;", "import org.jspecify.annotations.Nullable;") +``` + +--- + +### Part 3: ErrorProne Auto-Patching (`nullaway-patch`) +For the remaining manual nullness checks, configure ErrorProne's built-in **Auto-Patching tool** to scan your project, trace assignments, and write suggested `@Nullable` annotations directly to your source files. + +#### Declare Profile B: Auto-Patching (`nullaway-patch`) +Add this profile to your `pom.xml`: +```xml + + nullaway-patch + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + true + + -XDcompilePolicy=simple + -XDshould-stop.ifError=FLOW + -XDaddTypeAnnotationsToSymbol=true + + + -Xplugin:ErrorProne -XepDisableAllChecks -Xep:FieldMissingNullable:ERROR -Xep:ParameterMissingNullable:ERROR -Xep:ReturnMissingNullable:ERROR -Xep:EqualsMissingNullable:ERROR -XepPatchChecks:FieldMissingNullable,ParameterMissingNullable,ReturnMissingNullable,EqualsMissingNullable -XepPatchLocation:IN_PLACE -XepOpt:Nullness:DefaultNullnessAnnotation=org.jspecify.annotations.Nullable + + + -J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED + -J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED + -J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED + + + + com.google.errorprone + error_prone_core + ${errorprone.version} + + + com.uber.nullaway + nullaway + ${nullaway.version} + + + + + + + +``` +**Command to execute:** +```shell +mvn clean compile -Pnullaway-patch +``` +*(Verify changes using `git diff` after compilation)* + +--- + +## 3. Reference +https://buganizer.corp.google.com/issues/341380807