JDK-8356043 : Add lint warnings for unnecessary warning suppression
  • Type: CSR
  • Component: tools
  • Sub-Component: javac
  • Priority: P4
  • Status: Draft
  • Resolution: Unresolved
  • Submitted: 2025-05-01
  • Updated: 2025-05-10
Related Reports
CSR :  
Relates :  
Description
Summary
-------

Add a new compiler warning that warns about unnecessary warning suppressions in `@SuppressWarnings` annotations.

Problem
-------

The Java compiler emits warnings to alert developers about code that appears incorrect or imprudent. It is then up to the developer to apply human judgement to decide whether to change the code, ignore the warning, or suppress the warning.

For lint warnings (which is most of them), suppression is accomplished at a per-declaration level by `@SuppressWarnings` annotations and globally by the `-Xlint:-category` command line flag.

There are two problems with this mechanism.

The first problem is that `@SuppressWarnings` and `-Xlint:-category` flags are very blunt instruments. In fact, the latter is _maximally_ blunt: it covers the entire compilation. The former is still quite blunt, because while affected warnings occur at specific lines of code, the `@SuppressWarnings` annotation always applies to an entire class, method, or variable declaration.

This imprecision is a problem because that exessive coverage creates opportunity for false negatives. That is, warnings other than the particular one breing targeted could be suppressed unwittingly, creating an opportunity for uncaught bugs.

`@SuppressWarnings` annotations are sometimes added even when they're never actually needed, i.e., proactively as the code is written, because the developer thinks they _might_ be needed and wants to potentially save one compile & debug cycle. But the compiler provides the same feedback (i.e., none) whether or not there was actually any warning there, so this type of mistake is rarely caught.

The second problem is that once a warning is suppressed, the affected code can continue to evolve, but the continued need for the suppression is typically rarely, if ever, reviewed. This is due mostly to the "out of sight, out of mind" effect, but also even for a developer diligent enough to track this, doing such a review would require a tedious manual, per-warning audit. This also creates an opportunity for false negatives, i.e., more uncaught bugs.

Perhaps most frustrating, the compiler _by definition_ has all the information needed to detect unnecessary warning suppression, yet there's no way for developers to automatically extract or apply this information.

An analysis of the JDK reveals over 400 unnecessary suppressions currently being applied, so this problem is real.

Solution
--------

The solution is to enable the compiler to report when a lint category is being suppressed unnecessarily. This CSR proposes to warn only about unnecesssary warning suppression via `@SuppressWarnings` annotations; unnecessary suppression via `-Xlint:-category` flags is left for future study.

*Definition of Unnecessary Suppression*

First, it is important to be precise about the meaning of "unnecessary suppression". A _suppression_ is the occurrence of a lint category within a `@SuppressWarnings` annotation. Such a suppression is _unnecessary_ if recompiling with (a) that suppression removed and (b) that lint category enabled would _not_ result in the generation of new warning(s) in that lint category in any of the code covered by the suppression.

Condition (b) is because we want the determination of whether a suppression is unnecessary to be a function of the code itself, not the details of how a particular compiler run is configured. Therefore, the determination is always made as if the corresponding category were enabled. That is, disabling all lint categories doesn't suddenly make all suppressions unnecessary.

When it comes to reporting unnecessary suppressions, ideally the implementation is perfect, but if not, a crucial observation is that false positives are much worse than false negatives: a false negative is simply equivalent to the current behavior, while a false positive could put a developer in a situation where it would be impossible to ever build with no warnings (short of removing code). Therefore, it is critical that false positives be avoided. When given a choice, the implementation should err on the side of false negatives.

*Implementation of Unnecessary Suppression Warnings*

A new `suppression` lint category is added to allow developers to control whether this warning is enabled using the usual mechanism. Even though this warning relates to the suppression of other warnings, it is still just a normal compiler warning.

By default, the `suppression` lint category will be disabled: the `-Xlint` flag must be used to enable it.

Suppresion tracking and consequent warning generation is not enabled for all lint categories. The following lint categories are always excluded from unnecessary supression warnings: `path`, `options`, and `suppression` itself. Warnings will never be generated for unnecessary suppression of these categories.

The meaning of unnecessary suppression by a `@SuppressWarnings` annotation is clear except possibly in the scenario where a warning is suppressed by two levels of `@SuppressWarnings` annotations:
```
@SuppressWarnings("divzero")
public class A {

    @SuppressWarnings("divzero")
    public int m() {
        return 1/0;
    }
}
```

Clearly one of them is unnecessary, but which one? The implementation always assumes that the innermost annotation is the "real" one doing the suppression and any containing annotations are the "unnecessary" ones. In the above example, the outer annotation on `class A` would generate the warning. This behavior is consistent with the principle that warning suppression should be apply as narrowly as possible; removing the suppression indicated by the warning always ensures that this principle is applied.

The JLS recommends that unrecognized strings in `@SuppressWarnings` annotations be ignored. If a `@SuppressWarnings` category key is not recognized as a `LintCategory`, it will never generate an unnecessary suppression warning (that includes `doclint` keys, which are not `LintCategory`s).

As mentioned above, some lint categories are not tracked for unnecessary suppression: `path`, `options`, and `suppression` itself. Warnings will never be generated for suppression of these categories.

Other lint categories are tracked for unnecessary suppression, but don't support `@SuppressWarnings` (for example, `output-file-clash`). If a `@SuppressWarnings` category key is recognized and tracked but does not support suppression via `@SuppressWarnings`, then it will always generate an unnecessary suppression warning.

What about `@SuppressWarnings("suppression")`? The lint category `suppression` is itself excluded from suppression tracking, so this could never generate a warning for unnecessary suppression. However, the annotation is completely valid for other lint categories, where the usual thing happens: no unnecessary suppression warnings will be generated by that annotation or any `@SuppressWarnings` annotations on any nested declarations.

Specification
-------------

The output of `javac --help-lint` changes as follows:
```
--- x0	2025-05-01 14:38:06.521664879 -0500
+++ x1	2025-05-01 14:38:30.359932368 -0500
@@ -167,6 +167,8 @@
                          Also warn about other suspect declarations in Serializable and Externalizable classes and interfaces.
     static               Warn about accessing a static member using an instance.
     strictfp             Warn about unnecessary use of the strictfp modifier.
+    suppression          Warn about recognized @SuppressWarnings values that don't actually suppress any warnings.
     synchronization      Warn about synchronization attempts on instances of value-based classes.
     text-blocks          Warn about inconsistent white space characters in text block indentation.
     this-escape          Warn when a constructor invokes a method that could be overriden in an external subclass.
```

The warning for unneccessary suppression via `@SuppressWarnings` looks like this:
```
    $ javac -Xlint:suppression Test.java
    Test.java:10: warning: [suppression] unnecessary warning suppression: "divzero"
        @SuppressWarnings("divzero")
        ^
    1 warning
```

If multiple categories in the same annotation are being warned about, the warning looks like this:
```
    $ javac -Xlint:suppression Test.java
    Test.java:10: warning: [suppression] unnecessary warning suppression: "rawtypes", "unchecked"
        @SuppressWarnings({ "rawtypes", "unchecked" })
        ^
    1 warning
```

Comments
Updated to remove suppression tracking for suppression via `-Xlint:-foo` flags.
10-05-2025

> If synchronization is superseded by identity for the same category of checks, it could be challenging to get the same code base "meta-lint clean" on multiple compiler versions. As long as "synchronization" and "identity" point to the same `LintCategory` (as they will), then this won't be a problem - suppression tracking is based on the category not the option string or chosen alias. > Depending on how suppression is handled, there are also hazards for compiling under... True, but as [~mcimadamore] pointed out this is true for any newly introduced warning - and so the usual solution applies - i.e., you would add `@SuppressWarnings("suppression")` if the new warning starts popping up and you don't have time to fix it (or if you have time, you fix it and move on). Now I agree with you that there could be some different/new issues that pop up with the warning about unnecessary `-Xlint:-foo` flags. I think now we should just side-step those by only warning about suppression via `@SuppressWarnings` in this first version. We can wait until later to worry about adding support for warnings about suppression via `-Xlint:-foo`. Your thoughts?
09-05-2025

PS See JDK-8356539 for potential complications of a `suppression` type policy. If `synchronization` is superseded by `identity` for the same category of checks, it could be challenging to get the same code base "meta-lint clean" on multiple compiler versions. Depending on how suppression is handled, there are also hazards for compiling under `$JDK_N/bin/javac -X:lint:all -Werror ..` compared to `$JDK_(N+k)/bin/javac -X:lint:all -Werror --release $N ...`
09-05-2025

FYI I also asked for additional input on the `compiler-dev` mailing list [here][1]. [1]: https://mail.openjdk.org/pipermail/compiler-dev/2025-May/030223.html
07-05-2025

I think this warning will be valuable but your points make sense and they argue for not including `suppression` in `-Xlint:all`. Let's explore what that means: either (a) it's still a lint category but excepted from `all`, or (b) it's not a lint category anymore. The downside of (a) is that "all" no longer really mans "all", which might be confusing. The downside of (b) is that you lose the ability to say `@SuppressWarnings("suppression")` which is likely to be helpful in those very situations you allude to (e.g., compiling with different JDK versions). In addition, we would have to create some new flag like `-warn-unnecessary-suppression` or whatever. I think (a) is the better compromise. This exception could be reverted in the future if the warning was sufficiently popular. Until then, if you want all lint categories enabled *including* `suppression`, you would have to make that explicit via `-Xlint:all,suppression`. The javac help and man page would of course need to document which categories were excepted from `-Xlint:all`.
06-05-2025

A significant subset of the JDK code base is compiled under "-Xlint:all -Werror". I think this is a strong, simple property that is valuable to maintain. I don't know how many other nontrivial code bases follow suit. As alluded to the previous mailing list discussion, different Java compilers accept different sets of suppression strings, including different versions of `javac`, and different versions of `javac` and have semantically different checks for the same lint warning. Additionally, some warnings like deprecation and dependent on the version of JDK library being compiled against so a suppression of deprecation could be needed in some contexts but not others. Getting code to compile under `-Xlint:al -Werror` is already challenging and I don't think the build system trade offs to bundling extraneous suppression detection to the lint mechanism are advantageous. Those sort of checks feel to me more appropriate for an out-of-lint-band / opt-in mechanism.
06-05-2025

Hi [~acobbs]. I support code being clear of `javac` lint warnings (cf JEP 212: Resolve Lint and Doclint Warnings, JDK-8042878) and I think this proposal does address a gap in the platform capabilities to be able to get code clear of "meta-lint" conditions or to be not just "perfect" with respect to warnings but be "pluperfect." However, I do have reservations about how this mechanism is proposed to be implemented so I'm moving the CSR back to Draft. I'd like us to reconsider functionality in this area early in the JDK 26 timeline. That should give sufficient time to develop some alternatives and work through implications for more complicated compilation scenarios.
06-05-2025

[~acobbs], acknowledged. The CSR process is getting lots of submissions at the moment ahead of JDK 25 rampdown 1.
05-05-2025

[~darcy], the associated PR is still in draft and I'm not sure it will get done for JDK 25 but I wanted to go ahead and get this CSR in the pipeline in case it is. Thanks.
05-05-2025