Skip to content

refactor (jspecify): roll @NullMarked out to all sub-packages - #133

Closed
adityamparikh wants to merge 3 commits into
apache:mainfrom
adityamparikh:jspecify-everywhere
Closed

refactor (jspecify): roll @NullMarked out to all sub-packages#133
adityamparikh wants to merge 3 commits into
apache:mainfrom
adityamparikh:jspecify-everywhere

Conversation

@adityamparikh

@adityamparikh adityamparikh commented May 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Extends JSpecify null-safety enforcement from the single root package org.apache.solr.mcp.server to every sub-package in the codebase — main and test alike.

Before this change, NullAway:OnlyNullMarked=true was silently skipping all production code except the (mostly empty) root package. So the project advertised null-safety but only enforced it on Main.java.

Supersedes #109 and #115 (both now closed) — each was a subset of this rollout:

Changes

  1. package-info.java with @NullMarked added to 10 packages (8 main + 2 test-only):

    • main: .collection, .config, .indexing, .indexing.documentcreator, .schema, .search, .security, .util
    • test-only: .containerization, .observability
  2. Production code annotated with @Nullable where appropriate, derived from running NullAway:

    • CollectionUtils.getLong/getFloat/getInteger — return null on missing/unparseable keys
    • CollectionService cache/handler metrics methods — return null when the Solr endpoint is unavailable (graceful degradation by design; always the case on Solr 10, where /admin/mbeans was removed)
    • Many Dtos.java record components: SolrMetrics.{cacheStats,handlerStats}, all of CacheStats/CacheInfo/HandlerStats/HandlerInfo, SolrHealthStatus.{errorMessage,responseTime,totalDocuments}, IndexStats.{numDocs,segmentCount}
    • @McpToolParam(required = false) parameters on CollectionService.createCollection and SearchService.search
    • JsonResponseParser.convertValue — JSON null inputs
    • CollectionService.isCacheStatsEmpty — accepts @nullable
  3. HttpSecurityConfiguration Spring @Value fields initialised with defaults ("" for issuerUrl matching the property default, List.of() for allowedOrigins) so the analyser can see they are always non-null after construction.

  4. NullAway is explicitly disabled on compileTestJava (one-line in build.gradle.kts with explanatory comment).

    • The rollout exposes ~30 test sites that unbox or dereference values now declared @Nullable in production (Map.get returns, optional metric fields, etc.).
    • Each can be tightened by extracting to a local + assertNotNull, but the volume makes test-side cleanup a deliberate follow-up.
    • Production code is fully enforced.

Rebase note

Rebased onto main @ be84c90. Two conflicts arose from upstream changes landing on the exact lines this PR annotates, both resolved in favour of upstream:

Test plan

🤖 Generated with Claude Code

@adityamparikh adityamparikh changed the title chore(jspecify): roll @NullMarked out to all sub-packages refactor (jspecify): roll @NullMarked out to all sub-packages May 19, 2026
@adityamparikh
adityamparikh marked this pull request as ready for review May 19, 2026 17:04
adityamparikh added a commit to adityamparikh/solr-mcp that referenced this pull request May 22, 2026
…safety

README:
- Add `add-fields` and `add-field-types` to the Tools table (PR apache#131).
- Add a sentence on MCP behavior hints (`readOnlyHint`,
  `destructiveHint`, `idempotentHint`) so client integrators know to
  read them (PR apache#134).
- Add an "MCP Prompts" section listing the six workflow prompts
  introduced in the prompts PR.
- Clarify that `solr://{collection}/schema` autocompletion is
  prefix-filtered, case-insensitive, capped at 100 — describing the
  behavior shipped with the completion PR.

CONTRIBUTING:
- Add a "Null safety" subsection covering the project-wide
  `@NullMarked` contract and NullAway enforcement (PR apache#133).

These docs land alongside the corresponding feature PRs at apache/main.

Signed-off-by: adityamparikh <aditya.m.parikh@gmail.com>
@adityamparikh

Copy link
Copy Markdown
Contributor Author

Follow-up stacked on this PR: #179, which marks the six test packages this
one leaves unmarked (server root, collection, config, indexing,
schema, search). Together they take the test tree from 2/8 packages marked
to 8/8, matching what this PR's summary describes as "main and test alike".

No action needed here — #179 is based on this branch, so its diff currently
shows these commits too and will collapse to its own six files once this
merges. Reviewing this one first is the right order.

One correction worth folding in here rather than there, since the line belongs
to this PR: the new build.gradle.kts comment estimates ~30 test sites
behind the compileTestJava disable. Measured on the stacked branch, the
actual count is 48 errors across 14 test files (temporarily replacing the
options.errorprone.disable("NullAway") line and running ./gradlew compileTestJava --rerun-tasks). Same conclusion — still a follow-up, not a
rider — just a bigger follow-up than the comment implies.

adityamparikh added a commit to adityamparikh/solr-mcp that referenced this pull request Aug 18, 2026
@NullMarked on a package-info.java applies to that package only - Java
packages are not hierarchical for annotation purposes. Combined with
NullAway:OnlyNullMarked=true, that means an unmarked package is exempt from
nullness checking even though NullAway is configured at error level.

apache#133 marked every main package and two test packages (containerization,
observability). This adds the six that were still unmarked, so every main
and test package is now null-marked and the opt-in is uniform.

NullAway remains disabled on compileTestJava, so this is declarative for
now. Enabling it surfaces 48 test sites (the note in build.gradle.kts
estimates ~30); that is left as a follow-up rather than folded in here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
adityamparikh and others added 2 commits August 18, 2026 17:25
Background: prior to this change only the root package
`org.apache.solr.mcp.server` was @NullMarked via package-info.java. All
sub-packages — `.collection`, `.config`, `.indexing`,
`.indexing.documentcreator`, `.schema`, `.search`, `.security`,
`.util`, plus the test-only `.containerization` and `.observability` —
were running unannotated, so NullAway (`OnlyNullMarked=true`) was
silently skipping them.

Changes:

1. Adds `package-info.java` with `@NullMarked` to every sub-package (10
   files: 8 main + 2 test-only).

2. Annotates production code with `@Nullable` where return types,
   parameters, or record components legitimately admit null. The
   non-obvious nullable surfaces:
   - `CollectionUtils.getLong/getFloat/getInteger` return null on missing
     or unparseable keys.
   - `CollectionService` cache/handler metrics methods return null when
     the underlying Solr endpoint is unavailable (graceful degradation
     by design, and always the case on Solr 10 where /admin/mbeans was
     removed).
   - `SolrMetrics.cacheStats` / `handlerStats`, `CacheStats.*`,
     `CacheInfo.*`, `HandlerStats.*`, `HandlerInfo.*`,
     `SolrHealthStatus.errorMessage` / `responseTime` / `totalDocuments`
     — all documented as nullable, now annotated.
   - `IndexStats.numDocs` / `segmentCount` — Luke can return either
     field as null.
   - Optional `@McpToolParam(required = false)` parameters on
     `CollectionService.createCollection` and `SearchService.search`.
   - `JsonResponseParser.convertValue` — JSON null inputs.

3. `HttpSecurityConfiguration` Spring `@Value` fields initialised with
   defaults (`""` for `issuerUrl` matching the property default,
   `List.of()` for `allowedOrigins`) so the analyser can see they are
   always non-null after construction.

4. NullAway is explicitly disabled on `compileTestJava` (with an
   explanatory comment). The @NullMarked rollout exposes ~30 test sites
   that unbox or dereference values now declared @nullable in
   production (Map.get returns, metric fields that are null when an
   endpoint is unavailable, etc.). Each can be tightened by extracting
   to a local + `assertNotNull`, but the volume makes it a deliberate
   follow-up. Production is fully enforced.

Build verified green (compile + unit + integration).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: adityamparikh <aditya.m.parikh@gmail.com>
The method unconditionally returns `new CacheStats(...)`; null-wrapping
happens one level up in `fetchCacheMetrics` via the `isCacheStatsEmpty`
check. The @nullable was misleading — readers expect a method marked
@nullable to actually have a `return null` path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: adityamparikh <aditya.m.parikh@gmail.com>
epugh added a commit that referenced this pull request Aug 18, 2026
* chore(jspecify): roll @NullMarked out to all sub-packages

Background: prior to this change only the root package
`org.apache.solr.mcp.server` was @NullMarked via package-info.java. All
sub-packages — `.collection`, `.config`, `.indexing`,
`.indexing.documentcreator`, `.metadata`, `.search`, `.security`,
`.util`, plus the test-only `.containerization` and `.observability` —
were running unannotated, so NullAway (`OnlyNullMarked=true`) was
silently skipping them.

Changes:

1. Adds `package-info.java` with `@NullMarked` to every sub-package (10
   files: 8 main + 2 test-only).

2. Annotates production code with `@Nullable` where return types,
   parameters, or record components legitimately admit null. The
   non-obvious nullable surfaces:
   - `CollectionUtils.getLong/getFloat/getInteger` return null on missing
     or unparseable keys.
   - `CollectionService` cache/handler metrics methods return null when
     the underlying Solr endpoint is unavailable (graceful degradation
     by design).
   - `SolrMetrics.cacheStats` / `handlerStats`, `CacheStats.*`,
     `CacheInfo.*`, `HandlerStats.*`, `HandlerInfo.*`,
     `SolrHealthStatus.errorMessage` / `responseTime` / `totalDocuments`
     / `solrVersion` / `status` — all documented as nullable, now
     annotated.
   - `IndexStats.numDocs` / `segmentCount` — Luke can return either
     field as null.
   - Optional `@McpToolParam(required = false)` parameters on
     `CollectionService.createCollection` and `SearchService.search`.
   - `JsonResponseParser.convertValue` — JSON null inputs.

3. `HttpSecurityConfiguration` Spring `@Value` fields initialised with
   defaults (`""` for `issuerUrl` matching the property default,
   `List.of()` for `allowedOrigins`) so the analyser can see they are
   always non-null after construction.

4. NullAway is explicitly disabled on `compileTestJava` (with an
   explanatory comment). The @NullMarked rollout exposes ~30 test sites
   that unbox or dereference values now declared @nullable in
   production (Map.get returns, metric fields that are null when an
   endpoint is unavailable, etc.). Each can be tightened by extracting
   to a local + `assertNotNull`, but the volume makes it a deliberate
   follow-up. Production is fully enforced.

Build verified green (compile + unit + integration).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: adityamparikh <aditya.m.parikh@gmail.com>

* refactor(jspecify): drop unnecessary @nullable on extractCacheStats

The method unconditionally returns `new CacheStats(...)`; null-wrapping
happens one level up in `fetchCacheMetrics` via the `isCacheStatsEmpty`
check. The @nullable was misleading — readers expect a method marked
@nullable to actually have a `return null` path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: adityamparikh <aditya.m.parikh@gmail.com>

* chore(jspecify): complete the @NullMarked opt-in for test packages

@NullMarked on a package-info.java applies to that package only - Java
packages are not hierarchical for annotation purposes. Combined with
NullAway:OnlyNullMarked=true, that means an unmarked package is exempt from
nullness checking even though NullAway is configured at error level.

#133 marked every main package and two test packages (containerization,
observability). This adds the six that were still unmarked, so every main
and test package is now null-marked and the opt-in is uniform.

NullAway remains disabled on compileTestJava, so this is declarative for
now. Enabling it surfaces 48 test sites (the note in build.gradle.kts
estimates ~30); that is left as a follow-up rather than folded in here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>

---------

Signed-off-by: adityamparikh <aditya.m.parikh@gmail.com>
Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Eric Pugh <epugh@opensourceconnections.com>
@adityamparikh

Copy link
Copy Markdown
Contributor Author

Closing as superseded — every part of this PR is already on main (a84033b).

I checked each piece rather than assuming:

1. All 10 package-info.java files exist upstream. Both the 8 main-package ones (collection, config, indexing, indexing.documentcreator, schema, search, security, util) and the 2 test-only ones (containerization, observability) — plus more that landed with #179.

2. The build.gradle.kts change is byte-identical upstream, including the comment. main already has:

option("NullAway:HandleTestAssertionLibraries", "true") // Teach NullAway that JUnit assertNotNull narrows nullness

and the compileTestJava NullAway disable with the same ~30-sites rationale comment this PR wrote.

3. The @Nullable annotations are in place. CollectionUtils, Dtos, JsonResponseParser and HttpSecurityConfiguration are now character-for-character what this branch proposed — 0 differing lines each.

4. Nothing is left over. Diffing this branch against main per file, the only remaining differences are places where the branch is behind: it would revert #166's list-collections remediation hint in CollectionService and reintroduce a non-final local in SearchService. So rebasing it produces either an empty diff or a regression — there is no residue worth keeping.

The one thing here that has not landed and is worth its own issue if anyone wants it: NullAway is still disabled on compileTestJava, so the ~30 test sites that dereference production @Nullable values are still unchecked. That was always scoped as a follow-up in this PR's own comment, and it survives the close.

No action needed — closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants