bazelbuild / bazelbuild/bazel

JavaBuilder runs Error Prone without `-XDaddTypeAnnotationsToSymbol=true` on JDK ≤ 21, causing nullness false positives

Open Beginner friendly
#30,743 4 comments 0 reactions 0 assignees View on GitHub
P4 team-Rules-Java type: bug
Dominant language
Java
Stars
25.8k
Forks
4.6k
Avg merge
2d 20h
Merged PRs (30d)
72

Description

### Description of the bug:

When the Error Prone analyzer runs on a **JDK ≤ 21** tool runtime, Bazel's
JavaBuilder produces `NullArgumentForNonNullParameter` (and other nullness)
false positives on idiomatic nullable-annotated arguments, e.g. Guava:

```java
Iterables.getLast(list, null); // @ParametricNullness defaultValue
MoreObjects.toStringHelper(this).add("k", opt.orElse(null)); // JSpecify @Nullable value
```

```
error: [NullArgumentForNonNullParameter] Null is not permitted for parameter
'defaultValue' of method 'getLast'.
```

### Which category does this issue belong to?

Java Rules

### What's the simplest, easiest way to reproduce this bug? Please provide a minimal example if possible.

Reproducer repo: https://github.com/davido/bazel-issue-30743-repro

A one-file `java_library` calling `Iterables.getLast(xs, null)`, built with
`rules_java` 9.8.0 and `-Xep:NullArgumentForNonNullParameter:ERROR`, with a
custom `default_java_toolchain` that pins the analyzer JDK. Guava is vendored
(`guava.jar`) so no Maven fetch is needed; Bazel will still fetch `rules_java`
and the remote JDKs on first run unless they are already in the repository
cache.

```
git clone https://github.com/davido/bazel-issue-30743-repro
cd bazel-issue-30743-repro
bazelisk build //:repro # analyzer on remotejdk_21 (Zulu 21.0.12) -> FALSE POSITIVE
bazelisk build --config=jdk25 //:repro # analyzer on remotejdk_25 -> clean (fix default-on)
bazelisk build --config=flag21 //:repro # remotejdk_21 + -XDaddTypeAnnotationsToSymbol=true -> clean
```

### Which operating system are you running Bazel on?

MacOS

### What is the output of `bazel info release`?

9.2.0

### If `bazel info release` returns `development version` or `(@non-git)`, tell us how you built Bazel.

_No response_

### What's the output of `git remote get-url origin; git rev-parse HEAD` ?

```text

```

### If this is a regression, please try to identify the Bazel commit where the bug was introduced with bazelisk --bisect.

_No response_

### Have you found anything relevant by searching the web?

This is OpenJDK **JDK-8225377** — before JDK 22, javac does not attach type-use
annotations read from a dependency's class files to the corresponding parameter
symbols, so Error Prone's nullness analysis cannot see `@ParametricNullness` /
JSpecify `@Nullable` and treats the parameter as non-null. The javac fix is
opt-in via `-XDaddTypeAnnotationsToSymbol=true` on JDK 21.0.8+ and enabled by
default on JDK 22+.

Error Prone's documented `-Xplugin:ErrorProne` entry point **requires** this
flag on JDK ≤ 21 (it fails fast if the caller has not passed it — see
google/error-prone#5426), which is why Maven/Gradle Error Prone integrations
pass it. **JavaBuilder does not use `-Xplugin`** — it wires Error Prone through
its built-in integration
(`com.google.devtools.build.buildjar.javac.plugins.errorprone.ErrorPronePlugin`,
via `ErrorProneAnalyzer.createByScanningForPlugins`), which neither requires nor
supplies the flag. So on JDK ≤ 21 the analyzer silently runs without it and the
false positives surface. On JDK 22+ the fix is default-on and everything is
clean.

### Any other information, logs, or outputs that you want to share?

### Requested fix

Have JavaBuilder set `-XDaddTypeAnnotationsToSymbol=true` on the javac
invocation when the tool JDK feature version is ≤ 21 — matching the contract
Error Prone's `-Xplugin` path already enforces. On JDK 22+ it is a no-op.
Candidate patch (in `BlazeJavacMain.compile`, next to the existing
`options.put("expandJarClassPaths", "false")`):

```java
// JDK-8225377: before JDK 22, javac does not attach type-use annotations
// loaded from classpath class files to their symbols; Error Prone then emits
// nullness false positives on @ParametricNullness / JSpecify @Nullable params.
if (Runtime.version().feature() <= 21) {
options.put("addTypeAnnotationsToSymbol", "true");
}
```

### This has precedent in the same code

- `BlazeJavacMain` already uses `Runtime.version().feature()` (in its
JDK-too-old processor/system diagnostics), so the idiom is established here.
- JavaBuilder's `ErrorPronePlugin.processArgs` already injects an
Error-Prone-required argument the user did not pass
(`-XepIgnoreUnknownCheckNames`).
- The toolchain already passes Error-Prone-*required* javac flags —
`-XDcompilePolicy=simple` and `--should-stop=ifError=FLOW`.
`-XDaddTypeAnnotationsToSymbol=true` is the same category.

### We have a workaround — but this should be fixed upstream

A toolchain-level workaround exists and we [use](https://gerrit-review.googlesource.com/c/gerrit/+/619806) it: add the flag to `javacopts`,
or run the analyzer on JDK ≥ 22 (pin the toolchain `java_runtime` to a JDK 22+
runtime).

```python
default_java_toolchain(
name = "...",
javacopts = ["-XDaddTypeAnnotationsToSymbol=true"], # only needed for JDK <= 21 analyzer
...
)
```

But affected Bazel + Error Prone builds — those running the analyzer (the
toolchain tool JDK) on JDK 21 — hit this silently until each project discovers
and patches its own toolchain. Error Prone's own `-Xplugin`
integration *requires* the flag on JDK ≤ 21; JavaBuilder's built-in integration
should honour the same contract so the correct behaviour is the default, not an
opt-in. Hence we think it belongs in JavaBuilder, not in every consumer's
toolchain.

Contributor guide

Open the contributing guide

Research direction

Start with the linked reproducer and compare the three Bazel builds on JDK 21 and JDK 25. Then inspect BlazeJavacMain.compile near the existing expandJarClassPaths option and the ErrorPronePlugin argument handling. Done means the JDK ≤21 analyzer build is clean without a toolchain workaround while JDK 22+ behavior remains unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
build-system
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
75/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.