# NullAway: annotation-based null checking for Java builds

> NullAway is an Error Prone plugin that checks @Nullable annotations on every compile. It is fast enough to leave on, but it only sees the code you annotate, and the README is explicit that it does not prevent all NPEs.

**uber/NullAway** — A tool to help eliminate NullPointerExceptions (NPEs) in your Java code with low build-time overhead.

- Repository: https://github.com/uber/NullAway
- Stars: 4,112 · Forks: 369
- Language: Java
- License: MIT
- Published: 2026-08-04 · Updated: 2026-08-18 · Language: en
- Canonical page: https://hysenlabs.com/projects/uber-nullaway

## The problem NullAway targets: NPEs that survive review

A NullPointerException in Java is usually a local contract failure. Some method returns null, some caller assumes it does not, and the two facts live in different files. NullAway pushes that contract into the type system by asking you to annotate fields, parameters and return values that may be null, then checking dereferences against those annotations. The README frames the goal narrowly: it helps eliminate NPEs, it does not prove their absence.

The intended user is a team with an existing Java or Android codebase that already runs Error Prone. NullAway is a plugin to Error Prone, not a standalone analyzer, so it inherits that dependency. Teams that want a null checker without adopting Error Prone are not the audience. The README also states the design trade-off directly: NullAway catches most of the NPEs observed in production while keeping the annotation burden reasonable, which is a different promise from a full soundness checker.

## How the Error Prone plugin performs its checks

NullAway runs inside javac as an Error Prone check. You supply @Nullable annotations from a library such as JSpecify, and NullAway performs type-based local checks to confirm that any pointer dereferenced in checked code cannot be null. It is not a whole-program interprocedural analysis that runs separately from the build; it is a compiler plugin, which is why the README can describe it as fast.

The configuration hinges on one decision: which code is annotated and which is not. The README states that NullAway requires exactly one of AnnotatedPackages or OnlyNullMarked, because it needs that boundary to distinguish annotated from unannotated code. Inside the boundary, source is checked for null dereferences and for proper use of @Nullable, and class files in those packages are assumed to use @Nullable correctly. Outside it, NullAway stays quiet. This is the mechanism behind the incremental adoption story, and it is also the main source of surprises, because a package you forgot to list is a package you are not checking.

## Installing NullAway with Gradle and running a first check

NullAway requires a build on JDK 17 or higher with Error Prone 2.36.0 or higher. The README's instructions assume Gradle and point to the wiki for other build systems. You need the Gradle Error Prone plugin, the NullAway dependency, a source of nullability annotations (JSpecify is recommended), and Error Prone itself.

The following is the shape of the dependency block from the README, with versions left as placeholders because the README uses placeholders too:

```gradle
dependencies {
  errorprone "com.uber.nullaway:nullaway:<NullAway version>"
  api "org.jspecify:jspecify:1.0.0"
  errorprone "com.google.errorprone:error_prone_core:<Error Prone version>"
}
```

The compile task configuration is where the two required options appear. check("NullAway", CheckSeverity.ERROR) promotes NullAway findings from warnings to errors, and it is equivalent to the -Xep:NullAway:ERROR argument. option("NullAway:AnnotatedPackages", "com.uber") is equivalent to -XepOpt:NullAway:AnnotatedPackages=com.uber and marks the namespace to check.

```gradle
import net.ltgt.gradle.errorprone.CheckSeverity

tasks.withType(JavaCompile) {
  options.errorprone {
    check("NullAway", CheckSeverity.ERROR)
    option("NullAway:AnnotatedPackages", "com.uber")
  }
}
```

After the build runs, the reader should expect NullAway diagnostics to appear as compiler errors for dereferences of possibly-null values in the listed packages. The README notes that NullAway emits warnings by default, so without the check line you will see warnings rather than a failing build. It also mentions that you can try NullAway alone by disabling other Error Prone checks with options.errorprone.disableAllChecks, equivalent to passing -XepDisableAllChecks before the NullAway-specific arguments.

## Generated code, Android, and the Dagger version floor

Annotation processors that write into your own package namespace are the sharpest edge here. Dagger and AutoValue generate code that NullAway will check if it falls inside your AnnotatedPackages, and the README says errors in generated code will block the build when NullAway is set to ERROR. The recommended fix is to disable Error Prone entirely on generated code using the -XepExcludedPaths option added in Error Prone 2.1.3, configured in Gradle through options.errorprone.excludedPaths=. You have to determine which directory holds the generated sources and write a regex for it. That is a manual step, and getting the regex wrong means either unchecked generated code or a broken build.

Android has its own constraint. Versions 3.0.0 and later of the Gradle Error Prone plugin no longer support Android, so recent versions of that plugin need extra configuration; the repository's sample-app/build.gradle shows one approach, and the README warns your project may need tweaks. The 2.x line of the Gradle Error Prone plugin still supports Android. On the Android path you can drop JSpecify and use androidx.annotation.Nullable instead. There is also a version floor for Dagger: versions older than 2.12 can interact badly with NullAway, and the README points to Dagger 2.12 as the fix.

## Where NullAway is the wrong tool

The README is unusually candid that NullAway does not prevent all possible NPEs. It catches most of the NPEs the project has observed in production, while keeping annotation burden reasonable. Read that as a coverage limit, not a marketing hedge. Code you have not placed inside AnnotatedPackages, or outside a @NullMarked scope, is not analyzed for null dereferences at all.

Two other cases argue against it. If your build does not already use Error Prone, NullAway is not a drop-in; you are adopting Error Prone first, with its own configuration and its own set of checks. And if your problem is not nullness but a broader class of defects, or if you need soundness guarantees for a safety-critical component, a local, annotation-driven check with a deliberately bounded scope is the wrong instrument. The README's own framing, that NullAway offers good value for a reasonable annotation cost, is a statement about trade-offs, not about completeness.

## NullAway compared with the Checker Framework

The README names the Checker Framework and Eradicate as similar type-based null checkers for Java, and compares NullAway's approach to nullability checking in Kotlin and Swift. The difference that matters in practice is where the check runs and what it costs. NullAway is built as an Error Prone plugin specifically so it can run on every build, and the README reports build-time overhead usually under 10% in the project's measurements. A checker that runs as a separate, heavier analysis stage can afford deeper reasoning but is harder to keep on for every compile.

That is the real trade-off, not a feature checklist. If your team's constraint is that the analysis must not slow down local builds, the plugin architecture is the reason to pick NullAway. If your constraint is that the analysis must be as complete as possible and you can pay for it in build time, the README itself points you at the alternatives. Note that NullAway accepts any @Nullable annotation, including AndroidX and JetBrains annotations, so switching annotation libraries is not the deciding factor.

## Maintenance, upgrades, and the MIT licence

The repository is not archived, and the last push was on 2026-08-21, which is under a month before today's date. Releases are frequent: v0.14.0 on 2026-08-21, v0.13.8 on 2026-07-19, and v0.13.7 on 2026-06-16. That cadence matters because NullAway tracks Error Prone, and the README pins a minimum Error Prone version of 2.36.0. Upgrading Error Prone without a compatible NullAway, or the reverse, is the upgrade cost to plan for.

NullAway is licensed under MIT, and the LICENSE.txt file sits at the repository root. MIT is permissive, so the practical implication is that you can use NullAway in a closed-source build without a copyleft obligation, but this is a description of the licence identifier, not legal advice. The repository also carries a nullaway-bom module, which is the kind of artifact you would import to keep NullAway versions aligned across modules rather than repeating a version string in each build file. The README does not document a rollback procedure, so if a NullAway upgrade starts failing a build, the recovery path is your version control history, not a documented downgrade command.

## Conclusion

Adopt NullAway if you already build with Error Prone on JDK 17 or higher and can annotate one package at a time, starting with the AnnotatedPackages option. Do not adopt it if you cannot run Error Prone at all, or if you expect it to find nulls in code you have not annotated; it will not. Before rolling it out, verify that your Error Prone version is 2.36.0 or higher, that your generated sources are excluded from checking, and that your team accepts @Nullable annotations as part of the code review surface.

## FAQ

### What does @Nullable do in NullAway?

It marks a field, method parameter or return value that may be null. NullAway uses those annotations to check that any pointer dereferenced in annotated code cannot be null. NullAway accepts any @Nullable annotation, including JSpecify, AndroidX and JetBrains annotations.

### How do I use NullAway?

Add the Gradle Error Prone plugin, the NullAway dependency and a nullability annotation library, then configure the JavaCompile task with check("NullAway", CheckSeverity.ERROR) and option("NullAway:AnnotatedPackages", "com.uber"). The README states that NullAway requires exactly one of AnnotatedPackages or OnlyNullMarked to run.

### What is the difference between NullAway and JSpecify?

They are not competitors. JSpecify supplies the nullability annotations, such as org.jspecify.annotations.Nullable, and the README recommends it as the annotation source. NullAway is the Error Prone plugin that reads those annotations and reports null dereferences.

### How does NullAway compare with the Checker Framework?

The README lists the Checker Framework as a similar type-based null checker for Java. NullAway's distinguishing choice is that it is built as an Error Prone plugin so it can run on every build, with build-time overhead usually under 10% in the project's measurements.

## Sources

- [Official README](https://github.com/uber/NullAway#readme)
- [Project repository](https://github.com/uber/NullAway)
- [Release notes](https://github.com/uber/NullAway/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/uber-nullaway
