chore: Checker Framework adoption - part 1#1981
Merged
Merged
Conversation
karenc-bq
approved these changes
Jun 19, 2026
1a9dab7 to
2d63054
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Checker Framework adoption — Part 1 (core connection/plugin path)
This is part 1 of a planned series to incrementally adopt static nullness checking
in the wrapper, inspired by #373 ("Use checker framework to check for nulls"). That PR
demonstrated the value of the idea but tried to flip on three checkers across 20 files at
once and became an unmergeable WIP. This series takes the opposite approach: enable one
checker, in warning mode, scoped to one cohesive area at a time, and actually fix the
findings before widening scope.
Context
The codebase already uses
@Nullable/@NonNullannotations heavily (~420@Nullable,~670
@NonNull) and depends onchecker-qual, but nothing ever verified them — theorg.checkerframeworkGradle plugin was declared insettings.gradle.ktsbut neverapplied. So the annotations were documentation, not guarantees.
What this PR does
Wires up the NullnessChecker behind a flag. A guarded block in
wrapper/build.gradle.kts(active only with-PenableCheckerFramework) applies theplugin with:
-Awarns, build does not fail), and-AonlyDefsto the core path:ConnectionPluginManager,PluginServiceImpl,wrapper.ConnectionWrapper.Normal Java 8 builds are completely unaffected. Because the checker needs JDK 11+ to
run (the main source targets Java 8), it is intended to run on a JDK 17 lane; the build
block handles the JDK-17 plumbing (
--add-exports/--add-opensforjdk.compiler,and stripping the unsupported
-Xbootclasspath/parg the plugin injects).Adds an on-demand GitHub Action (
.github/workflows/run-checker-framework.yml,workflow_dispatchonly). On GitHub's runners JDK 17 is available natively, so theworkflow is simple: check out, set up JDK 8 + 17, run
:aws-advanced-jdbc-wrapper:compileJava -PenableCheckerFramework. It posts a findingssummary (total / style-only / real / crashes, with a by-category breakdown) to the job
summary and uploads the full report as an artifact. It is not wired into CI gating —
it is triggered manually to measure nullness findings.
Fixes the findings in the scoped files. Running the checker surfaced 25 real
(non-style) findings; all are resolved, taking the scoped files to 0 real findings,
0 checker crashes. Highlights:
PluginServiceImpl.getCurrentHostSpec()— it dereferencedthe result of
Utils.getWriter(...)(which is@Nullable) before null-checking it,so a topology with no writer would NPE. Restructured to resolve into a local,
null-check, then use; dead fallback code removed.
PluginPipelinewas overloaded forboth the execute path (always has a continuation
func) and the notify path (ignoresit), forcing
functo be@Nullableand making the execute path look unsafe. Splitout a dedicated
NotifyPluginPipelineso each contract is honest.@Nullableapplied to fields, parameters, andreturns that were already null in practice (
effectiveConnProvider,hosts,currentHostSpec,routedHostSpec,pooledConnection,getPlugin,ExceptionManagerdialect params,
Utils.isNullOrEmpty,Pairtype bounds, etc.).@SuppressWarningswhere the honest fixis unavailable (overriding the non-null JDK
Wrapper.unwrap), an upstream tool bug(generic varargs inference crash), or out of scope (widening
StateSnapshotProvideracross ~25 implementers).
Utils.isNullOrEmptyalso gained@EnsuresNonNullIfso its guard semantics areunderstood at every call site.
Why warning mode / why scoped
This is intentionally non-blocking. The goal of part 1 is to prove the workflow,
clean up the most safety-critical path, and gather real data — not to gate CI yet. Once a
package is clean, a later part can flip it from
-Awarnsto build-failing.Verification
PluginServiceImplTests+ConnectionPluginManagerTests(44 tests) pass —confirming the pipeline split and
getCurrentHostSpecrefactor preserve behavior.output is 80
type.anno.before.modifierstyle warnings (annotation placement — notbugs, mechanically auto-fixable, deferred).
A methodology + findings writeup is included under
docs/checker-framework-pilot/.Planned next parts
type.anno.before.modifierplacement warnings.-AonlyDefspackage-by-package; flip cleaned packages to build-failing.StateSnapshotProviderinterfacewidening.
How to run the checker
On GitHub: Actions → Run Checker Framework (Nullness) → Run workflow (manual dispatch).
By submitting this pull request, I confirm that my contribution is made under the terms
of the Apache 2.0 license.