# Technical Documentation: uber/NullAway

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/uber/NullAway)

### Overview (`README.md`)

## NullAway: Fast Annotation-Based Null Checking for Java [](https://github.com/uber/nullaway/actions/workflows/continuous-integration.yml) [](https://codecov.io/github/uber/NullAway?branch=master)

NullAway is a tool to help eliminate `NullPointerException`s (NPEs) in your Java code.  To use NullAway, first add `@Nullable` annotations in your code wherever a field, method parameter, or return value may be `null`.  Given these annotations, NullAway performs a series of type-based, local checks to ensure that any pointer that gets dereferenced in your code cannot be `null`.  NullAway is similar to the type-based nullability checking in the Kotlin and Swift languages, and the [Checker Framework](https://checkerframework.org/) and [Eradicate](https://fbinfer.com/docs/1.1.0/checker-eradicate/) null checkers for Java.

NullAway is *fast*.  It is built as a plugin to [Error Prone](http://errorprone.info/) and can run on every single build of your code.  In our measurements, the build-time overhead of running NullAway is usually less than 10%.  NullAway is also *practical*: it does not prevent all possible NPEs in your code, but it catches most of the NPEs we have observed in production while imposing a reasonable annotation burden, giving a great "bang for your buck."

## Installation

### Overview

NullAway requires that you build your code with JDK 17 or higher and [Error Prone](http://errorprone.info), version 2.36.0 or higher.  See the [Error Prone documentation](http://errorprone.info/docs/installation) for instructions on getting started with Error Prone and integration with your build system.  The instructions below assume you are using Gradle; see [the docs](https://github.com/uber/NullAway/wiki/Configuration#other-build-systems) for discussion of other build systems.  If you are building with JSpecify mode enabled, we recommend building with the most recent JDK available; see [the wiki docs on JSpecify support](https://github.com/uber/NullAway/wiki/JSpecify-Support) for more details.

### Gradle

#### Java (non-Android)

To integrate NullAway into your non-Android Java project, add the following to your `build.gradle` file:

```gradle
plugins {
  // we assume you are already using the Java plugin
  id "net.ltgt.errorprone" version "<plugin version>"
}

dependencies {
  errorprone "com.uber.nullaway:nullaway:<NullAway version>"

  // Some source of nullability annotations; JSpecify recommended,
  // but others supported as well.
  api "org.jspecify:jspecify:1.0.0"

  errorprone "com.google.errorprone:error_prone_core:<Error Prone version>"
}

import net.ltgt.gradle.errorprone.CheckSeverity

tasks.withType(JavaCompile) {
  options.errorprone {
    check("NullAway", CheckSeverity.ERROR)
    option("NullAway:AnnotatedPackages", "com.uber")
  }
  // Include to disable NullAway on test code
  if (name.toLowerCase().contains("test")) {
    options.errorprone {
      disable("NullAway")
    }
  }
}
```

Let's walk through this script step by step.  The `plugins` section pulls in the [Gradle Error Prone plugin](https://github.com/tbroyer/gradle-errorprone-plugin) for Error Prone integration.

In `dependencies`, the first `errorprone` line loads NullAway, and the `api` line loads the [JSpecify](https://jspecify.dev) library which provides suitable nullability annotations, e.g., `org.jspecify.annotations.Nullable`.  NullAway allows for any `@Nullable` annotation to be used, so, e.g., `@Nullable` from the AndroidX annotations Library or JetBrains annotations is also fine. The second `errorprone` line sets the version of Error Prone is used.

Finally, in the `tasks.withType(JavaCompile)` section, we pass some configuration options to NullAway.  First `check("NullAway", CheckSeverity.ERROR)` sets NullAway issues to the error level (it's equivalent to the `-Xep:NullAway:ERROR` standard Error Prone argument); by default NullAway emits warnings.  Then, `option("NullAway:AnnotatedPackages", "com.uber")` (equivalent to the `-XepOpt:NullAway:AnnotatedPackages=com.uber` standard Error Prone argument) tells NullAway that source code in packages under the `com.uber` namespace should be checked for null dereferences and proper usage of `@Nullable` annotations, and that class files in these packages should be assumed to have correct usage of `@Nullable` (see [the docs](https://github.com/uber/NullAway/wiki/Configuration) for more detail).  NullAway requires exactly one of the `AnnotatedPackages` or `OnlyNullMarked` configuration arguments to run, in order to distinguish between annotated and unannotated code.  See [the configuration docs](https://github.com/uber/NullAway/wiki/Configuration) for more details and other useful configuration options.  For even simpler configuration of NullAway options, use the [Gradle NullAway plugin](https://github.com/tbroyer/gradle-nullaway-plugin).  Finally, we show how to disable NullAway on test code, if desired.

We recommend addressing all the issues that Error Prone reports, particularly those reported as errors (rather than warnings).  But, if you'd like to try out NullAway without running other Error Prone checks, you can use `options.errorprone.disableAllChecks` (equivalent to passing `"-XepDisableAllChecks"` to the compiler, before the NullAway-specific arguments).

#### Android

Versions 3.0.0 and later of the Gradle Error Prone Plugin [no longer support Android](https://github.com/tbroyer/gradle-errorprone-plugin/releases/tag/v3.0.0).  So if you're using a recent version of this plugin, you'll need to add some further configuration to run Error Prone and NullAway.  Our [sample app `build.gradle` file](https://github.com/uber/NullAway/blob/master/sample-app/build.gradle) shows one way to do this, but your Android project may require tweaks.  Alternately, 2.x versions of the Gradle Error Prone Plugin still support Android and may still work with your project.

Beyond that, compared to the Java configuration, the JSpecify dependency can be removed; you can use the `androidx.annotation.Nullable` annotation from the AndroidX annotation library instead.

#### Annotation Processors / Generated Code

Some annotation processors like [Dagger](https://google.github.io/dagger/) and [AutoValue](https://github.com/google/auto/tree/master/value) generate code into the same package namespace as your own code.  This can cause problems when setting NullAway to the `ERROR` level as suggested above, since errors in this generated code will block the build.  Currently the best solution to this problem is to completely disable Error Prone on generated code, using the `-XepExcludedPaths` option added in Error Prone 2.1.3 (documented [here](http://errorprone.info/docs/flags), use `options.errorprone.excludedPaths=` in Gradle).  To use, figure out which directory contains the generated code, and add that directory to the excluded path regex.

**Note for Dagger users**: Dagger versions older than 2.12 can have bad interactions with NullAway; see [here](https://github.com/uber/NullAway/issues/48#issuecomment-340018409).  Please update to Dagger 2.12 to fix the problem.

#### JSpecify Mode / Guava

As of version 33.4.1, [Guava](https://github.com/google/guava) uses [JSpecify](https://jspecify.dev) `@NullMarked` annotations for most of its packages, and hence NullAway will treat those packages as annotated by default.  This treatment may lead to some false positives (due to handling of type variables), which can be mitigated by running NullAway in its (under-development) JSpecify mode.  See [the wiki docs on JSpecify support](https://github.com/uber/NullAway/wiki/JSpecify-Support) for more details.

#### Lombok

Unlike other annotation processors above, Lombok modifies the in-memory AST of the code it processes, which is the source of numerous incompatibilities with Error Prone and, consequently, NullAway. 

We do not particularly recommend using NullAway with Lombok. However, NullAway encodes some knowledge of common Lombok annotations and we do try for best-effort compatibility. In particular, common usages like `@lombok.Builder` and `@Data` classes should be supported.

Since Lombok v1.18.34, Lombok adds `@lombok.Generated` by default to the methods/classes it generates. NullAway relies on this annotation to detect generated code within the in-memory Java AST and will ignore (i.e. not check) the implementation of this generated code, treating it as unannotated.
If you have explicitly disabled this behavior then NullAway will not correctly recognize Lombok-generated code, and compatibility may break.

If you are using a Lombok version earlier than 1.18.34, you must enable the following configuration option in an applicable `lombok.config` file:

```properties
lombok.addLombokGeneratedAnnotation = true
```

## Code Example

Let's see how NullAway works on a simple code example:
```java
static void log(Object x) {
    System.out.println(x.toString());
}
static void foo() {
    log(null);
}
```
This code is buggy: when `foo()` is called, the subsequent call to `log()` will fail with an NPE.  You can see this error in the NullAway sample app by running:
```bash
cp sample/src/main/java/com/uber/mylib/MyClass.java.buggy sample/src/main/java/com/uber/mylib/MyClass.java
./gradlew build
```

By default, NullAway assumes every method parameter, return value, and field is _non-null_, i.e., it can never be assigned a `null` value.  In the above code, the `x` parameter of `log()` is assumed to be non-null.  So, NullAway reports the following error:
```
warning: [NullAway] passing @Nullable parameter 'null' where @NonNull is required
    log(null);
        ^
```
We can fix this error by allowing `null` to be passed to `log()`, with a `@Nullable` annotation:
```java
static void log(@Nullable Object x) {
    System.out.println(x.toString());
}
```
With this annotation, NullAway points out the possible null dereference:
```
warning: [NullAway] dereferenced expression 'x' is @Nullable
    System.out.println(x.toString());
                        ^
```
We can fix this warning by adding a null check:
```java
static void log(@Nullable Object x) {
    if (x != null) {
        System.out.println(x.toString());
    }
}
```
With this change, all the NullAway warnings are fixed.

For more details on NullAway's checks, error messages, and limitations, see [our detailed guide](https://github.com/uber/NullAway/wiki).

## Support

Please feel free to [open a GitHub issue](https://github.com/uber/NullAway/issues) if you have any questions on how to use NullAway.  Or, you can [join the NullAway Discord server](https://discord.gg/QH2F779) and ask us a question there.

## Contributors

We'd love for you to contribute to NullAway!  Please note that once
you create a pull request, you will be asked to sign our [Uber Contributor License Agreement](https://docs.google.com/a/uber.com/forms/d/1pAwS_-dA1KhPlfxzYLBqK6rsSWwRwH95OCCZrcsY5rk/viewform).

## License

NullAway is licensed under the MIT license.  See the LICENSE.txt file for more information.

---

### Overview (`jdk-javac-plugin/README.md`)

# Javac plugin for generating JSpecify JDK astubx file

This module and others contain logic to generate an astubx file from the
[annotated JSpecify JDK](https://github.com/jspecify/jdk).  The generation works
in two stages:

1. This module provides a javac plugin that gets injected into the build of the
JSpecify JDK.  It generates `.json` files capturing the nullability annotations
in the JDK.
2. We have a separate astubx generator (main entrypoint:
`com.uber.nullaway.jdkannotations.AstubxGeneratorCLI`) that turns the `.json`
files into an `.astubx` file.  

To regenerate the file and run the main NullAway tests and self-check, run:

```bash
./jdk-javac-plugin/regenerate-jspecify-jdk.sh 
```

The script fetches a shallow checkout of the JSpecify JDK at the pinned
`JDK_REVISION` in the script. To reuse an existing clean checkout at that
revision, add `--jdk-dir /path/to/jdk`. You can pass `--boot-jdk /path/to/boot-jdk`
to give a path to the boot JDK to use to bootstrap the build;
without it, JDK configure searches for a compatible installed JDK.
Configuration, build output, and JSON files go into a fresh temporary directory.
Failed runs retain that directory and their logs; use `--keep-temp` to retain it
after successful runs as well. Use `--verbose` to stream command output while
also saving it to the logs.  If all succeeds, the
`nullaway/src/main/resources/jspecify-jdk.astubx` file will be overwritten with
the new version.

---

### Overview (`jar-infer/jar-infer-cli/README.md`)

NullAway jar-infer
=======

### Introduction

This extends NullAway to support Nullability inference on external libraries.

### Requirements

  * Java 8
  * The [Gradle](https://gradle.org/) build tool
  * The [WALA](http://wala.sourceforge.net/wiki/index.php/Main_Page) analysis framework

### Installation

Clone the repository, and then:

    gradle wrapper
    ./gradlew build

This will pull in the required WALA jars and build the analysis code.

### Usage

    java -jar <path-to-jar-infer-cli-tool> -i <in_path> -o <out_path> [-p <pkg_name>] [-vdh]
     -i,--input-file <in_path>     path to target jar/aar file
     -o,--output-file <out_path>   path to processed jar/aar file
     -p,--package <pkg_name>       qualified package name
     -v,--verbose                  set verbosity
     -d,--debug                    print debug information
     -h,--help                     print usage information

---

### Overview (`jdk-annotations/jdk-integration-test/README.md`)

These integration tests for the javac plugin work as follows.  We have two versions of code used as a "library" for
input, in the sibling `test-annotated` and `test-unannotated` modules.  For the `test-annotated` code, we generate an
astubx using the javac plugin (to generate JSON) and the astubx generator (to generate astubx from the JSON).  Then,
when building the jar for the `test-unannotated` code, we include the generated astubx in the jar file.  This module
depends only on the `test-unannotated` module, so the tests are run against the unannotated code, but with the astubx
available on the classpath.  This allows us to test that the javac plugin is correctly generating the astubx and that
the astubx is correctly being loaded and used by NullAway.

---

### Overview (`scripts/nullaway-suppression-remover/README.md`)

# NullAway suppression remover

`suppression-remover` removes unnecessary checker entries from Java
`@SuppressWarnings` annotations. It temporarily removes every matching suppression,
compiles the target module, and restores the suppressions required to make the build
pass. The remaining source changes are the suppressions that the tool determined were
unnecessary.  The technique is a heuristic, and is not guaranteed to remove all
unnecessary suppressions.

Although it was written for NullAway, the tool could be used with another Error
Prone checker whose diagnostics use the standard `[CheckerName]` format (this has
not been tested).

## Prerequisites

- Python 3.10 or newer
- A working build for the project being processed
- For the default build behavior, a Gradle project with either `gradlew` at its
  root or `gradle` on `PATH`

Install the command from this directory. Using a virtual environment keeps its
Python dependencies isolated:

```bash
cd scripts/nullaway-suppression-remover
python3 -m venv /tmp/nullaway-suppression-remover-venv
source /tmp/nullaway-suppression-remover-venv/bin/activate
python -m pip install -e .
```

The `suppression-remover` command is then available whenever that virtual
environment is active.

## Before running it

The tool edits Java source files in place and does not have a dry-run mode. Run it
from a clean Git worktree so that you can inspect or discard its changes. Also run
the relevant build once before using the tool; pre-existing build failures can
prevent it from determining which suppressions are needed.

Avoid interrupting the process while a build is running. The tool normally restores
files when it cannot produce a passing build, but an external interruption may leave
the temporary edits in place.

## Usage

From anywhere inside the project to process, run:

```text
suppression-remover MODULE CHECKER [OPTIONS]
```

`MODULE` is the module directory relative to the project root, and `CHECKER` is
the name printed in compiler diagnostics. For example, from the root of this
repository:

```bash
suppression-remover nullaway NullAway --alt-suppression NullAway.Init
```

This scans every `.java` file below `nullaway/src`, treats both `NullAway` and
`NullAway.Init` as NullAway suppressions, and updates unnecessary annotations.

To process a whole project, including all of its submodules, use `.` for the module
and provide a build command that compiles everything:

```bash
suppression-remover . NullAway --build-cmd "./gradlew build --continue"
```

In this mode, the tool scans for Java files beneath every directory named `src`
under the project root. It trusts that the supplied build command compiles all of
those sources and reports checker errors for them. This mode also works for a
single-module project. A custom build command is required when the module is `.`.

After the command succeeds, inspect the resulting changes and run the project's
normal test or verification command before committing them.

### Options

`--alt-suppression ALT`
: Treat `ALT` as another suppression name for the same checker. The option can be
  repeated, for example:

  ```bash
  suppression-remover app NullAway \
    --alt-suppression NullAway.Init \
    --alt-suppression NullAway.Optional
  ```

`--project-root DIR`
: Use `DIR` as the project root. Without this option, the tool walks upward from
  the current directory and selects the first directory containing one of
  `settings.gradle`, `settings.gradle.kts`, `build.gradle`, `build.gradle.kts`, or
  `pom.xml`.

  ```bash
  suppression-remover subprojects/service NullAway --project-root /path/to/project
  ```

`--build-cmd CMD`
: Run `CMD` instead of the generated Gradle command. The command must compile all
  source sets that should be checked, emit standard Java/Error Prone diagnostics,
  and exit nonzero when checker errors occur.

  This option is required for Maven and other non-Gradle builds, for processing a
  whole project with `MODULE` set to `.`, and for unusual Gradle layouts:

  ```bash
  suppression-remover service NullAway \
    --build-cmd "mvn -pl service test-compile"
  ```

  The value is split into arguments using shell-like quoting, but it is not run
  through a shell. Shell operators such as `&&`, pipes, redirections, and environment
  assignments are therefore not supported; put complex build logic in a script and
  pass that script as the command instead.

`--max-iterations N`
: Set the maximum number of restore-and-verify iterations after the initial build.
  The default is `5`, and the value must be a positive integer. Increase it for
  codebases where nested or interacting suppressions require more refinement passes,
  or when removing suppressions across many modules:

  ```bash
  suppression-remover . NullAway \
    --build-cmd "./gradlew build --continue" \
    --max-iterations 10
  ```

`--source-encoding ENCODING`
: Read and write Java source files using `ENCODING` (default: `utf-8`). Use a
  Python codec name such as `iso-8859-1` for projects with legacy source files:

  ```bash
  suppression-remover app NullAway --source-encoding iso-8859-1
  ```

Use `suppression-remover --help` to display the command-line help.

## Default Gradle build

For a specific non-root module, when `--build-cmd` is omitted, the tool discovers
source sets containing Java files under `MODULE/src` and runs their compile tasks
with `--continue`. For a module named `service` with `main` and `test` source sets,
the generated command is equivalent to:

```bash
./gradlew :service:compileJava :service:compileTestJava --continue
```

A custom source set such as `testFixtures` is mapped to
`compileTestFixturesJava`.

## What gets changed

Only `@SuppressWarnings` annotations containing an exact matching string are
considered. If the target checker is the only entry, the entire annotation is
removed. If an annotation also suppresses other checkers, it is rewritten with
those entries retained. For example:

```java
@SuppressWarnings({"NullAway", "deprecation"})
```

becomes:

```java
@SuppressWarnings("deprecation")
```

The tool parses Java syntax, so text in comments and string literals is ignored.
When it removes one value from a multiline annotation, it preserves the annotation's
multiline layout and indentation. It may normalize a single-line multi-checker
annotation that it updates.  Trailing comments are removed.

## Running the tool's tests

Install the test dependency and run `pytest` from this directory:

```bash
python -m pip install -e '.[test]'
python -m pytest
```

---

--- METRICS ---
- Files Extracted: 5
- Estimated Token Budget: ~5248 tokens
- Canonical Reference: https://codewiki.google/github.com/uber/NullAway
