uber/NullAway
A tool to help eliminate NullPointerExceptions (NPEs) in your Java code with low build-time overhead
Healthy across the board
weakest axisPermissive license, no critical CVEs, actively maintained — safe to depend on.
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓Last commit 1d ago
- ✓14 active contributors
- ✓MIT licensed
Show all 6 evidence items →Show less
- ✓CI configured
- ✓Tests present
- ⚠Concentrated ownership — top contributor handles 76% of recent commits
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Healthy" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/uber/nullaway)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/uber/nullaway on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: uber/NullAway
Generated by RepoPilot · 2026-05-09 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/uber/NullAway shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
GO — Healthy across the board
- Last commit 1d ago
- 14 active contributors
- MIT licensed
- CI configured
- Tests present
- ⚠ Concentrated ownership — top contributor handles 76% of recent commits
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live uber/NullAway
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/uber/NullAway.
What it runs against: a local clone of uber/NullAway — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in uber/NullAway | Confirms the artifact applies here, not a fork |
| 2 | License is still MIT | Catches relicense before you depend on it |
| 3 | Default branch master exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 31 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of uber/NullAway. If you don't
# have one yet, run these first:
#
# git clone https://github.com/uber/NullAway.git
# cd NullAway
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of uber/NullAway and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "uber/NullAway(\\.git)?\\b" \\
&& ok "origin remote is uber/NullAway" \\
|| miss "origin remote is not uber/NullAway (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
&& ok "license is MIT" \\
|| miss "license drift — was MIT at generation time"
# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
&& ok "default branch master exists" \\
|| miss "default branch master no longer exists"
# 4. Critical files exist
test -f "annotations/src/main/java/com/uber/nullaway/annotations/Initializer.java" \\
&& ok "annotations/src/main/java/com/uber/nullaway/annotations/Initializer.java" \\
|| miss "missing critical file: annotations/src/main/java/com/uber/nullaway/annotations/Initializer.java"
test -f "annotations/src/main/java/com/uber/nullaway/annotations/Nullable.java" \\
&& ok "annotations/src/main/java/com/uber/nullaway/annotations/Nullable.java" \\
|| miss "missing critical file: annotations/src/main/java/com/uber/nullaway/annotations/Nullable.java"
test -f "annotations/src/main/java/com/uber/nullaway/annotations/RequiresNonNull.java" \\
&& ok "annotations/src/main/java/com/uber/nullaway/annotations/RequiresNonNull.java" \\
|| miss "missing critical file: annotations/src/main/java/com/uber/nullaway/annotations/RequiresNonNull.java"
test -f "annotations/src/main/java/com/uber/nullaway/annotations/EnsuresNonNull.java" \\
&& ok "annotations/src/main/java/com/uber/nullaway/annotations/EnsuresNonNull.java" \\
|| miss "missing critical file: annotations/src/main/java/com/uber/nullaway/annotations/EnsuresNonNull.java"
test -f "annotations/src/main/java/com/uber/nullaway/annotations/MonotonicNonNull.java" \\
&& ok "annotations/src/main/java/com/uber/nullaway/annotations/MonotonicNonNull.java" \\
|| miss "missing critical file: annotations/src/main/java/com/uber/nullaway/annotations/MonotonicNonNull.java"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 31 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~1d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/uber/NullAway"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
NullAway is an Error Prone plugin that performs fast, annotation-based null-pointer checking for Java code at compile time. It uses @Nullable annotations on fields, parameters, and return values to perform type-based, local flow analysis that catches most real-world NullPointerExceptions with <10% build-time overhead, bridging the gap between full null-safety (Kotlin/Swift) and no checking. Gradle monorepo: annotations/ module provides the core @Nullable, @MonotonicNonNull, @EnsuresNonNull marker annotations (JDK 11-compatible); main checker code likely in a checker/ or sibling module (not fully listed); buildSrc/ contains shared Gradle conventions (nullaway.java-test-conventions.gradle); code-coverage-report/ aggregates CI metrics; .github/workflows/ automates benchmarks and integration testing.
👥Who it's for
Java platform engineers and build/tooling teams at large companies (Uber's internal use case) who want to incrementally add null-safety to existing codebases without rewriting in Kotlin, and open-source Java library maintainers seeking low-cost NPE prevention with minimal annotation burden.
🌱Maturity & risk
Production-ready and actively maintained. The repo shows a mature Gradle/multi-module structure with comprehensive CI via .github/workflows/continuous-integration.yml, code coverage tracking (codecov.yml), and multiple release channels (RELEASING.md suggests formal versioning). The 2.6M lines of Java code and detailed configuration across annotations/, buildSrc/, and code-coverage-report/ indicate years of real-world hardening.
Relatively low-risk for adoption: single upstream dependency (Error Prone 2.36.0+), tight coupling to JDK 17+ (version constraint visible in README), and no apparent single-maintainer bottleneck given Uber's engineering scale. Main risk is Error Prone API instability or breaking changes in Error Prone versions—the tool cannot be used with older Error Prone toolchains.
Active areas of work
Active development visible through multiple GitHub Actions workflows: continuous-integration.yml runs on every commit, jmh-benchmark.yml tracks performance, and PR-specific benchmarks (run_pr_benchmarks.sh) suggest ongoing optimization. CLAUDE.md presence suggests recent AI-assisted development or documentation. Formal release process (RELEASING.md) and CHANGELOG.md indicate regular versioning.
🚀Get running
git clone https://github.com/uber/NullAway.git
cd NullAway
./gradlew build
./gradlew test
Daily commands: This is a compiler plugin, not a server. For local development:
./gradlew build # Compile all modules
./gradlew test # Run unit tests
./gradlew jmhBenchmark # Run JMH performance benchmarks (if applicable)
./gradlew publishToMavenLocal # Install locally for testing in other projects
🗺️Map of the codebase
annotations/src/main/java/com/uber/nullaway/annotations/Initializer.java— Core annotation marking fields that are initialized in constructor; fundamental to NullAway's initialization tracking logicannotations/src/main/java/com/uber/nullaway/annotations/Nullable.java— Primary annotation for marking nullable fields/parameters; understanding this is essential for using NullAway across the entire systemannotations/src/main/java/com/uber/nullaway/annotations/RequiresNonNull.java— Annotation for method preconditions; critical for flow-sensitive null checking that NullAway performsannotations/src/main/java/com/uber/nullaway/annotations/EnsuresNonNull.java— Annotation for method postconditions; enables NullAway to track non-null guarantees across method boundariesannotations/src/main/java/com/uber/nullaway/annotations/MonotonicNonNull.java— Annotation for fields that transition from null to non-null once; essential for lazy initialization patternsbuild.gradle— Root build configuration defining dependencies and plugin setup; necessary to understand how Error Prone and NullAway integrategradle/libs.versions.toml— Centralized dependency version catalog; required to understand toolchain versions and compatibility constraints
🛠️How to make changes
Add a new nullability annotation
- Create the annotation interface in annotations module (
annotations/src/main/java/com/uber/nullaway/annotations/YourNewAnnotation.java) - Document the annotation behavior and use cases in README.md (
README.md) - Add changelog entry describing the new annotation (
CHANGELOG.md) - Export annotation from module-info.java if not already exported (
annotations/src/main/java/module-info.java)
Add support for a new Android SDK version
- Create new jar-infer module directory following the pattern of existing SDK directories (
jar-infer/android-jarinfer-models-sdk32/build.gradle) - Generate or add the jarinfer.astubx file with null-safety models for the new SDK (
jar-infer/android-jarinfer-models-sdk32/src/main/resources/jarinfer.astubx) - Create AndroidJarInferModels.java entry class for the new SDK (
jar-infer/android-jarinfer-models-sdk32/src/main/java/com/uber/nullaway/jarinfer/AndroidJarInferModels.java) - Add the new module to root build.gradle in the module inclusion list (
build.gradle)
Run benchmarks and add performance tests
- Execute the main benchmark workflow on your branch (
.github/workflows/run_main_benchmarks.sh) - Create or modify test class in guava-recent-unit-tests for new benchmark scenarios (
guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java) - Trigger JMH benchmarking workflow via GitHub Actions (
.github/workflows/jmh-benchmark.yml)
🔧Why these technologies
- Error Prone Framework — Pluggable compiler-based checker system allowing NullAway to integrate into standard Java compilation without external tools or expensive interprocedural analysis
- JVM Annotations (@Nullable, @RequiresNonNull, etc.) — Lightweight, runtime-retainable metadata that developers apply declaratively; enables local type-based checking without complex control-flow analysis
- Gradle + buildSrc conventions — Standardizes build across multiple Android SDK versions and modules; manages dependency versions centrally to ensure consistency
- JarInfer AST stub files (.astubx) — Compact model format for third-party library null-safety without source code; critical for Android framework and Guava type information
- JMH Micro-benchmarking — Validates build-time overhead claims; ensures NullAway maintains sub-5% compilation slowdown across releases
⚖️Trade-offs already made
-
Local, type-based checking instead of interprocedural whole-program analysis
- Why: Keeps overhead minimal and build-time fast, enabling adoption on every build
- Consequence: Cannot detect null-safety violations across method/module boundaries without explicit annotations; developers must annotate entry points
-
Opt-in @Nullable annotations rather than 'non-null by default'
- Why: Allows gradual adoption on existing codebases without annotating millions of lines
- Consequence: Requires discipline and code reviews to ensure consistent annotation; unannotated code is assumed non-null but may not be
-
Separate jar-infer modules for each Android SDK version
- Why: Captures precise null-safety contracts for each SDK level (28–31+), preventing false positives
- Consequence: Maintenance burden for new SDK versions; each SDK release requires a new model artifact
-
Plugin architecture via Error Prone rather than standalone tool
- Why: Integrates seamlessly into standard Maven/Gradle builds; no separate invocation needed
- Consequence: Tightly coupled to Error Prone versions and compiler APIs; limits portability to non-JVM languages
🚫Non-goals (don't propose these)
- Does not handle runtime null safety or dynamic null checks
- Does not perform interprocedural or whole-program analysis
- Does not auto-generate or infer annotations; requires explicit developer annotation
- Does not guarantee absence of all NullPointerExceptions (only checked patterns)
- Not a replacement for testing or runtime validation frameworks
- Does not support non-JVM languages or compilation systems
🪤Traps & gotchas
- JDK 17+ required (per README): Java 11 is supported only for the annotations module (
sourceCompatibility = JavaVersion.VERSION_11in annotations/build.gradle), but the checker itself needs JDK 17+. 2. Error Prone version lock: NullAway is tightly coupled to Error Prone 2.36.0+; using an older or significantly newer Error Prone may cause API incompatibility—check/gradle/libs.versions.tomlbefore upgrading. 3. AnnotatedPackages must be configured: The checker requires explicitAnnotatedPackagesGradle option (shown in README gradle config) or it will not run; missing this option silently disables checking. 4. No built-in suppression syntax: suppressing NullAway warnings requires@SuppressWarnings("NullAway"), not javadoc-style@SuppressLint—easy to miss in migration from lint-based tools. 5. Flow analysis is intra-procedural: NullAway does not track state across method calls unless annotated with@EnsuresNonNullor similar contracts; unannotated libraries appear to export nullability by default.
🏗️Architecture
💡Concepts to learn
- Pluggable Type System — NullAway is implemented as a plugin to Error Prone's pluggable checker framework; understanding how type-checkers hook into the compiler is key to extending or debugging NullAway.
- Flow-Sensitive Type Refinement — NullAway performs local, intra-procedural analysis to refine nullability across control-flow paths (e.g., after null checks); this is the core algorithm that makes it practical despite not being flow-insensitive like some lint tools.
- Annotation-Based Contracts — Annotations like
@EnsuresNonNulland@RequiresNonNullencode semantic contracts across method boundaries, allowing the checker to propagate nullability info without whole-program analysis. - Monotonic Types —
@MonotonicNonNullcaptures the pattern where a field is null-initialized and then set to non-null exactly once (e.g., lazy-init); understanding this reduces false positives and annotation burden. - Annotation Processing (JSR 269) — NullAway leverages Java's pluggable annotation processor API to hook into javac and Error Prone; knowing how annotation processors work is essential for understanding the codebase architecture.
- Local Type Inference / Bidirectional Checking — The checker infers nullability of local variables based on assignments and control flow without explicit annotation; this reduces annotation overhead but requires careful handling of generics and lambdas.
- Build-Time Overhead Profiling (JMH Benchmarks) — NullAway's key promise is <10% build-time overhead; the repo's jmh-benchmark.yml and run_pr_benchmarks.sh indicate that performance regression testing is a first-class concern.
🔗Related repos
google/error-prone— The core compiler plugin framework that NullAway builds on top of; NullAway is a domain-specific Check for Error Prone.jspecify/jspecify— The recommended nullability annotation standard that NullAway supports; provides cross-tool portable@Nullableand@NonNullsemantics.Checker-Framework/checker-framework— Alternative pluggable type-checker for Java (similar goal to NullAway) with more comprehensive null-safety guarantees but slower build time; mentioned in README as a comparison.facebook/infer— Facebooks's NPE detection tool (Eradicate checker for Java); mentioned in README as a predecessor approach using different analysis techniques (not plugin-based).JetBrains/kotlin— The Kotlin language whose null-safety design (non-null by default with?for nullable) inspired NullAway's annotation-based approach for Java.
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add comprehensive test coverage for jar-infer Android SDK28 models
The jar-infer/android-jarinfer-models-sdk28 module exists in the file structure but has no visible test files in guava-recent-unit-tests or elsewhere. Given that NullAway's jar-infer models are critical for inferring nullness from external libraries, this module needs dedicated unit tests to validate that Android SDK28 method signatures are correctly analyzed.
- [ ] Create jar-infer/android-jarinfer-models-sdk28/src/test/java directory structure
- [ ] Write tests for common Android SDK28 methods (e.g., Intent, Bundle, Context) that jar-infer should model
- [ ] Add integration tests in jar-infer/android-jarinfer-models-sdk28/build.gradle to verify model generation
- [ ] Reference CONTRIBUTING.md for test conventions and run against existing test suite
Add GitHub Actions workflow for testing against multiple Java versions (11, 17, 21)
The annotations module explicitly targets Java 11 compatibility (sourceCompatibility = JavaVersion.VERSION_11), but continuous-integration.yml likely only tests against one JDK version. Modern Java development requires testing against multiple LTS versions. This ensures NullAway remains compatible as clients upgrade their Java versions.
- [ ] Review current .github/workflows/continuous-integration.yml to identify tested Java versions
- [ ] Create a new matrix job in continuous-integration.yml testing against Java 11, 17, and 21
- [ ] Ensure annotations module and buildSrc modules pass on all versions
- [ ] Update CONTRIBUTING.md with Java version testing requirements
Document and test the @EnsuresNonNull and @EnsuresNonNullIf annotation usage patterns
The annotations module includes advanced features like EnsuresNonNull.java and EnsuresNonNullIf.java, but there's no dedicated test module demonstrating proper usage patterns. Contributors and adopters need clear examples. The guava-recent-unit-tests pattern shows NullAway can have specialized test modules.
- [ ] Create a new module: annotations-usage-examples with proper build.gradle and directory structure
- [ ] Write test cases in src/test/java showing @EnsuresNonNull on builder patterns and validation methods
- [ ] Write test cases showing @EnsuresNonNullIf on conditional methods (e.g., Optional.isPresent() checks)
- [ ] Reference and link from CONTRIBUTING.md and README.md's annotation section
🌿Good first issues
- Add test coverage for
MonotonicNonNull.javaedge cases (e.g., double assignment, reflection-based null assignment). Start in the main checker module's test suite and look forMonotonicNonNullTestor similar—add a parameterized test case for races or unusual initialization patterns. - Document the JSpecify migration path: README mentions JSpecify 1.0.0 is recommended, but existing projects using
javax.annotation.Nullableneed guidance. Create a MIGRATION.md file in the root with before/after examples and a grep-based search-and-replace guide. - Extend the
Initializer.javaannotation to support constructor-chaining patterns (e.g.,this()calls that delegate initialization). File a test case showing the current false positive, then patch the checker logic to handle chained constructor nullability flow.
⭐Top contributors
Click to expand
Top contributors
- @msridhar — 76 commits
- @seemantasaha — 6 commits
- @yuxincs — 4 commits
- @salmanmkc — 2 commits
- @haewiful — 2 commits
📝Recent commits
Click to expand
Recent commits
dff4b58— Improve inference failure error message (#1567) (msridhar)740ffc6— Test case for trick to assert chain of accesses is non-null (#1566) (msridhar)c25a84d— Use Temurin JDK 17 always in CI (#1565) (msridhar)aea62eb— Use text blocks for addInputLines and addOutputLines calls (#1564) (msridhar)39c4dd9— Use text blocks in more tests (#1563) (msridhar)0dcdf2d— Update spotless and guava-latest deps, removed unused semver4j (#1562) (msridhar)05c6ae6— Enable generic bytecode tests on JDK 17 (#1561) (msridhar)5c2780c— Fix nullability for return and parameter wildcards (#1558) (msridhar)8f7c7e5— Fix wildcard inference bug with method references (#1553) (msridhar)29a896a— Initial inference for wildcards (#1549) (msridhar)
🔒Security observations
The NullAway repository demonstrates good security hygiene overall. As a null-checking tool/library, the codebase focuses on static analysis rather than handling sensitive data. No hardcoded credentials, SQL injection risks, or XSS vulnerabilities were identified in the provided file structure. The main concerns are minor: ensuring Gradle wrapper integrity, hardening shell scripts with proper error handling, and maintaining pre-commit hook security. The project follows standard Java/Gradle conventions with proper licensing and dependency management through gradle plugins. No exposed ports, missing security headers, or obvious Docker misconfigurations were found. The modular structure with separate annotation modules and test suites indicates good code organization practices.
- Medium · Gradle Wrapper JAR in Repository —
gradle/wrapper/gradle-wrapper.jar. The gradle-wrapper.jar file is committed to the repository. While Gradle wrapper is generally safe, best practice is to verify checksums and ensure the JAR hasn't been tampered with. This could potentially be a vector for supply chain attacks if the jar is compromised. Fix: Consider adding gradle-wrapper.jar to .gitignore and documenting the checksum verification process. Alternatively, ensure CI/CD validates wrapper integrity using gradle-wrapper.properties checksum verification. - Low · Shell Scripts Without Strict Error Handling —
.github/workflows/*.sh, .buildscript/check_git_clean.sh, .github/workflows/gcloud_ssh.sh, .github/workflows/get_repo_details.sh, .github/workflows/run_gcp_benchmarks.sh, .github/workflows/run_main_benchmarks.sh, .github/workflows/run_pr_benchmarks.sh. Several shell scripts in the .github/workflows and .buildscript directories may lack strict error handling (set -e, set -u, set -o pipefail). This could lead to silent failures or unexpected behavior if intermediate commands fail. Fix: Add set -euo pipefail at the beginning of all shell scripts to ensure they fail fast on errors. Review scripts for proper input validation and quoting of variables. - Low · Pre-commit Hook Configuration —
config/hooks/pre-commit, config/hooks/pre-commit-stub. The presence of pre-commit and pre-commit-stub files suggests git hooks are in use. If these aren't properly configured or enforced, developers may bypass security checks. Fix: Ensure pre-commit hooks are properly documented, maintained, and that developers understand their purpose. Consider using a tool like husky or pre-commit framework for better management.
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.