Skip to content

Enable the javac -Xlint categories that Error Prone cannot see - #67

Merged
ThoSap merged 12 commits into
mainfrom
enable-javac-lint-categories-and-fix-deprecations
Oct 22, 2026
Merged

ThoSap merged 12 commits into
mainfrom
enable-javac-lint-categories-and-fix-deprecations

Conversation

@ThoSap

@ThoSap ThoSap commented Sep 6, 2026 •

Copy link
Copy Markdown
Member

Follow-up to the Checkstyle removal #66
Error Prone reads the typed javac AST, so it cannot see everything the compiler sees.
This PR turns on the 17 lint categories that javac checks and Error Prone does not fully cover, fixes the 5 warnings they surfaced, and adds -Werror.

The build now compiles with 0 warnings and 0 notes, and -Werror keeps it there.

Why

Error Prone and javac lint look at different things:

Error Prone javac -Xlint
Input The typed AST - types, symbols, dataflow, a parsed Javadoc AST The compiler's own view: erasure, deprecation metadata, class-file resolution, raw source text
Owns Everything expressible on the AST, bug rules and style rules alike Type-system results, the deprecation machinery, language-evolution traps, and the build itself

The 90 Error Prone checks cover the AST side well. This PR closes the other side.
It deliberately does not enable a category that an Error Prone check fully owns, so no problem is reported twice.

Where Error Prone fully covers a rule, Error Prone wins: it has per-check severity, so a rule fails the build on its own. A javac lint category can only warn, so this PR adds -Werror.

The 17 enabled categories

Code quality (14)

Category Why it earns its place here
deprecation Was reported only as a summary note before, so 3 real hits sat unseen
removal API marked for removal. Already on by default in javac; listed so the policy is explicit
unchecked Unchecked conversions. Also only a note before
cast Unnecessary casts. No Error Prone counterpart exists
rawtypes Raw type use. No Error Prone counterpart exists
divzero Division by constant zero. No Error Prone counterpart exists
this-escape A constructor calls a method an external subclass could override. Relevant with Lombok generating constructors
identity Value-based classes where identity is expected. The JDK 25 name; synchronization is now a deprecated alias. Error Prone's ValueClassIdentity (ERROR) covers synchronized and more, but javac alone sees a value-based argument to a @RequiresIdentity parameter, such as new WeakReference<>(Integer.valueOf(1)) or Cleaner.register(...), and a value-based type argument in a declaration, such as WeakHashMap<Integer, String>
overloads Overloads that a lambda makes ambiguous. FunctionalInterfaceClash reports Consumer<String> against Function<String, String>, but not Consumer<String> against IntConsumer. javac reports both
varargs A redundant @SafeVarargs on a reifiable varargs type, and an unsafe use of the varargs array in a @SafeVarargs method. No Error Prone counterpart exists
try An explicit close() on a try-with-resources resource, a resource that the body never references, and a resource whose close() can throw InterruptedException. No Error Prone counterpart exists
text-blocks Inconsistent whitespace in text-block indentation. The project writes SQL in text blocks
dangling-doc-comments A doc comment attached to no declaration. The best fit for this codebase, which documents with /// markdown comments
restricted A call to a restricted method: the FFM downcall surface, MemorySegment.reinterpret, System.load and System.loadLibrary. Zero use today, so this is pure insurance

Build hygiene (3)

Category Why
options A bad command-line option, such as a stale --release or source/target pair
path An invalid classpath or sourcepath element
output-file-clash Two outputs collide. On Windows/macOS the filesystem is case-insensitive, so this is a real local-versus-CI trap

The categories left off, and why

A. Error Prone fully owns it - 8 categories

Enabling these in javac would report the same problem twice, and at a lower severity. Every Error Prone check below runs at ERROR.
Each row was verified with a probe. The 11 categories of the former section A have 17 javac warning keys in the JDK 25 CompilerProperties, and the probe triggered 16 of them, with the categories and Error Prone enabled together. For the 8 categories below, every javac warning had an Error Prone error on the same construct. The key that was not probed is static.not.qualified.by.type2, a static member of an anonymous class accessed through an expression.

javac category Owned by Match
empty EmptyIf exact
dep-ann DepAnn exact. Neither tool reads @deprecated in a /// Markdown comment
fallthrough FallThrough exact
finally Finally exact
lossy-conversions NarrowingCompoundAssignment exact, and stricter: it also reports byteVar += 1
overrides Overrides, EqualsHashCode exact - varargs disagreeing on an override, and equals without hashCode
static StaticQualifiedUsingExpression exact
auxiliaryclass MultipleTopLevelClasses, ClassName exact in effect: an auxiliary class cannot exist once no file holds a second top-level class

There is also a severity argument. javac has no per-category severity: a lint category can only warn unless -Werror promotes every warning at once. An Error Prone check fails the build on its own.

overloads, varargs and try were in this section before. The probe showed javac warnings with no Error Prone counterpart, so they moved to the enabled list. All three report 0 warnings on the codebase.

Verification of overloads, varargs and try

A second probe compiled the same constructs with -XepAllDisabledChecksAsWarnings, so every Error Prone 2.51.0 check ran, also the ones that are off by default. TraditionalJavadocToMarkdown had to stay off, because it crashes on DatabaseSpec (see #69).

javac warning Probe Error Prone result with every check on
overloads: potentially ambiguous overload Consumer<String> / Function<String, String> FunctionalInterfaceClash reports it too
overloads: potentially ambiguous overload Consumer<String> / IntConsumer, static and instance Nothing
(no javac warning) Supplier<String> / Callable<String> FunctionalInterfaceClash reports it, javac does not
varargs: redundant @SafeVarargs @SafeVarargs on String... Nothing
varargs: unsafe use of the varargs parameter @SafeVarargs <T> void m(List<T>... a) stores a in a field Nothing
try: explicit close() on a resource in.close() inside the try block Nothing
try: resource never referenced resource named in Nothing. UnusedVariable skips resources on purpose
try: resource never referenced resource named unused UnnamedVariable reports it, but only because of the name: its flag OnlyRenameVariablesNamedUnused defaults to true
try: close() can throw InterruptedException AutoCloseable whose close() declares it Nothing on the resource. InterruptedExceptionSwallowed reported the enclosing method only because it declares throws Exception

The source of Error Prone 2.51.0 agrees: no check reads @SafeVarargs, and the only checks that look at try-with-resources resources are TryWithResourcesVariable, UnnamedVariable and ClosingStandardOutputStreams.
So FunctionalInterfaceClash and javac overloads overlap in part, and each one reports a case that the other does not. Both stay on.

B. Third-party noise - 3 categories

Category Measured Why it stays off
classfile 37 warnings All from microprofile-config-api-3.1.jar (35) and microprofile-health-api-4.0.1.jar (2). They reference OSGi annotations - org.osgi.annotation.bundle.Requirement, Requirements, versioning.Version - that are not on the compile classpath. Not our code, not fixable here.
serial 6 warnings All six CRD classes: Grant, Role, Schema, Database, DefaultPrivilege, ClusterConnection. They inherit Serializable from the fabric8 CustomResource, but they travel as JSON and are never Java-serialized. A serialVersionUID on each would be pure ceremony
processing 2 warnings "No processor claimed any of these annotations", an artefact of the Lombok and Quarkus processor setup. Not actionable

C. Not applicable - 8 categories

module, exports, opens, requires-automatic, requires-transitive-automatic and missing-explicit-ctor all need a named module.
The project has no module-info.java. incubating needs an incubator module, and preview needs a preview language feature.
Neither is used, and preview is on by default anyway.

D. Deliberate aliases and near-misses

  • synchronization is not listed.
    It is a deprecated alias of identity since JDK 25, and the toolchain's own --help-lint says so. identity is listed instead.
  • strictfp is not listed.
    It flags an unnecessary strictfp modifier, but all floating-point has been strict since Java 17, so the modifier has been meaningless for four releases.
    For it to fire, somebody would have to type a keyword that does nothing, in a codebase with zero float and double declarations.
    It is harmless to add and guards a mistake nobody here can make.

The 5 warnings that were fixed

removal - 1

GrantReconcilerTest: SQLDataType.BIGINT.identity(true) → generatedByDefaultAsIdentity().
The jOOQ method is deprecated and marked for removal.
Worth noting for a reviewer: removal is on by default, so this warning was already visible before this PR. Nobody had looked.

deprecation - 3

HelmTest: Serialization.yamlMapper() twice and Serialization.jsonMapper() once, replaced by unmarshal(...) on the client's KubernetesSerialization instance (kubernetesClient.getKubernetesSerialization()), which is not deprecated. The two YAML reads pass a Jackson TypeReference, so their two @SuppressWarnings("unchecked") are gone as well.
Reading also moves from File to Files.newInputStream. fabric8 closes the stream itself: unmarshal(InputStream, TypeReference) wraps it in try-with-resources, and the Class overload calls that method.

unchecked - 1

ClusterConnectionReconcilerErrorTest: the mock creation carried //noinspection unchecked, an IntelliJ-only comment that javac ignores.
It now uses the reified mock() of Mockito 5, which infers the generic type from the assignment target, so no suppression is needed at all.

Review notes

  • removal is redundant today, the same way two Error Prone entries are. javac enables it by default. It is listed so the intent is explicit and survives a JDK that changes the default.
  • -Werror is on. The build is at zero warnings, so the ratchet holds today. javac promotes every warning: a deprecation that arrives with the next jOOQ or fabric8 bump fails the build, and so does any Error Prone check left at its default WARNING.
  • With -Werror, a javac lint warning hides the Error Prone findings of the same compilation. javac counts the warning as an error and stops before Error Prone runs. Error Prone reports its findings in the next build, after the javac warning is fixed.
  • The overlap with the Error Prone config is small and deliberate. The 8 categories in section A are absent from the -Xlint list because a check in errorprone.args fully covers them. The enabled categories overlap only in part: identity with ValueClassIdentity on synchronized, and overloads with FunctionalInterfaceClash on same-arity functional interfaces. There, both tools report the same construct.

Test scope

  • ./gradlew --rerun-tasks :operator:compileJava :operator:compileTestJava :generated:compileJava - BUILD SUCCESSFUL, 0 warnings, 0 notes.
  • Every category was measured with -Xlint:all on the JDK 25 Amazon Corretto toolchain across all three source sets, with the warning cap raised so nothing was truncated. The category list came from that toolchain's own javac --help-lint, not from documentation.
  • -Werror was proven live: a temporary Thread.getId() call failed the build with [deprecation], and a temporary a || b && c failed it with Error Prone's [OperatorPrecedence], a check at its default WARNING.
  • No production source changed. All fixes are in three test source files.

`HelmTest` uses the static, non-deprecated `Serialization.unmarshal` with
a Jackson `TypeReference`, so the `KubernetesSerialization` constant and
two `@SuppressWarnings("unchecked")` go away.

`ClusterConnectionReconcilerErrorTest` uses the reified `mock()` of
Mockito 5, which needs no suppression and no temporary variable.
@ThoSap
ThoSap force-pushed the enable-javac-lint-categories-and-fix-deprecations branch from 8efda4d to e0a353f Compare September 11, 2026 11:48
…sion, add README lead sentence

Lombok added lombok.checkReturnValueAnnotation in 1.18.48, so the new
lombok.config line had no effect on 1.18.46. The checkstyle version entry
had no remaining reference. The README Setup heading lost its lead sentence
when the registry section was removed.

@stplasim stplasim left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one thing, the HelmTest fix swaps one deprecated API for another. Rest looks good to me

@ThoSap

ThoSap commented Oct 5, 2026

Copy link
Copy Markdown
Member Author

Just one thing, the HelmTest fix swaps one deprecated API for another. Rest looks good to me

@stplasim Good catch, thanks. Fixed in f982915.

The static Serialization.unmarshal(InputStream, TypeReference) is deprecated in fabric8 7.6.1, with the hint to use KubernetesSerialization#unmarshal(InputStream, TypeReference). HelmTest now reads Chart.yaml, values.yaml and values.schema.json through kubernetesClient.getKubernetesSerialization(). As a side effect, the input streams are now closed, because KubernetesSerialization.unmarshal closes them itself.

It slipped through because -Xlint:deprecation only reports a warning: compileTestJava printed two [deprecation] warnings for HelmTest.java:88 and :97, but the build stayed green. That is a small, real example of the point from the #66 discussion that a warning goes unnoticed easily. After the fix, compileTestJava --rerun-tasks reports no warnings.

@ThoSap
ThoSap requested a review from stplasim October 5, 2026 14:42

@stplasim stplasim left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Nit: the PR description still says the deprecated calls were replaced by the static Serialization.unmarshal(...), and it says “4 warnings fixed” while listing 5.
On -Werror: this PR shows how easily a warning gets missed.

Base automatically changed from replace-checkstyle-rules-with-error-prone to main October 22, 2026 09:43
Assisted-by: Claude:claude-opus-5-5
Error Prone covers these categories only in part. A probe showed javac
warnings with no Error Prone counterpart, for example an unreferenced
try-with-resources variable or a redundant @SafeVarargs.

Assisted-by: Claude:claude-opus-5-5
@ThoSap
ThoSap merged commit 70242fc into main Oct 22, 2026
7 of 8 checks passed
@ThoSap
ThoSap deleted the enable-javac-lint-categories-and-fix-deprecations branch October 22, 2026 10:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants