Skip to content

Add JSpecify null annotations#379

Open
jodastephen wants to merge 4 commits into
mainfrom
jspecify
Open

Add JSpecify null annotations#379
jodastephen wants to merge 4 commits into
mainfrom
jspecify

Conversation

@jodastephen

@jodastephen jodastephen commented Jun 3, 2026

Copy link
Copy Markdown
Member

Introduce JSpecify nullness annotations to ThreeTen-Extra.

JSpecify is becoming the de facto standard for nullness annotations in Java. It allows methods and fields to be marked as accepting null, with a non-null default.

This change introduces the annotations to the main code, not the tests.

See #170

Summary by CodeRabbit

  • Chores
    • Added an optional compile-time nullability annotation dependency.
    • Applied JSpecify nullability annotations broadly across the public API and package-level defaults.
    • Strengthened null-safety metadata and added defensive null checks for deserialization to improve static analysis and API clarity.

Copilot AI review requested due to automatic review settings June 3, 2026 23:03
@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@jodastephen, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 28 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 41c1768e-bbce-4b49-b543-5e106e974b7f

📥 Commits

Reviewing files that changed from the base of the PR and between 0620ed1 and 06aa755.

📒 Files selected for processing (2)
  • src/changes/changes.xml
  • src/main/java/org/threeten/extra/chrono/AccountingChronologyBuilder.java
📝 Walkthrough

Walkthrough

Adds JSpecify: dependency and static module requirement, package-level @NullMarked annotations, numerous @Nullable annotations on parameters/returns/fields across temporal, chronology, and scale types, and adds some readResolve non-null checks.

Changes

JSpecify Nullability Annotations

Layer / File(s) Summary
Dependency and module descriptor setup
pom.xml, src/main/java/module-info.java
JSpecify dependency added as optional compile-scoped dependency with jspecify.version=1.0.0; requires static org.jspecify; added to module descriptor.
Package-level nullability defaults
src/main/java/org/threeten/extra/package-info.java, src/main/java/org/threeten/extra/chrono/package-info.java, src/main/java/org/threeten/extra/scale/package-info.java
@NullMarked added to three package-info.java files to establish package-wide nullness defaults.
Nullability annotations on instant/scale types
src/main/java/org/threeten/extra/scale/* (TaiInstant.java, UtcInstant.java, package-info)
@Nullable added to equals parameters, cached toString field, and package-level @NullMarked applied.
Nullability annotations on chronology and date types
src/main/java/org/threeten/extra/chrono/* (AccountingChronology, AccountingChronologyBuilder, AccountingDate, BritishCutoverChronology, BritishCutoverDate, InternationalFixedChronology, Symmetry010Chronology, Symmetry454Chronology, AbstractDate, etc.)
@Nullable added to getCalendarType return types, equals parameters, builder fields; several readResolve() methods now perform Objects.requireNonNull checks.
Nullability annotations on core temporal types
src/main/java/org/threeten/extra/* (AmPm, DayOfMonth, DayOfYear, Days, Half, HourMinute, Hours, Interval, LocalDateRange, Minutes, Months, MutableClock, OffsetDate, PackedFields, PeriodDuration, Quarter, Seconds, TemporalFields, Weeks, YearHalf, YearQuarter, YearWeek, Years, etc.)
@Nullable annotations added to isSupported parameter overloads, equals parameters, resolve return types, internal nullable fields, and readResolve non-null validations where applicable.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through code with tiny paws,
Marked nulls where silence once did pause,
A dot, a tag, a careful check,
Now signatures whisper "nullable" — heck!
Hooray for tidy, annotated laws.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add JSpecify null annotations' clearly and concisely summarizes the main change: introducing JSpecify nullness annotations throughout the codebase.
Docstring Coverage ✅ Passed Docstring coverage is 96.30% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jspecify

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI 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.

Pull request overview

This pull request introduces JSpecify nullness annotations across the main ThreeTen-Extra codebase by applying package-level non-null defaults (@NullMarked) and adding targeted @Nullable annotations where null is permitted (notably equals(Object) parameters, selected isSupported(...) parameters that explicitly allow null, and resolver methods that may return null).

Changes:

  • Add org.jspecify as an optional/static annotation dependency (Maven + JPMS) to support JSpecify usage.
  • Apply @NullMarked at package level for org.threeten.extra, org.threeten.extra.chrono, and org.threeten.extra.scale.
  • Add @Nullable annotations to align method signatures/fields with existing “null returns false” and “may return null” behavior.

Reviewed changes

Copilot reviewed 39 out of 39 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/main/java/org/threeten/extra/YearWeek.java Annotates isSupported(...) parameters and equals(...) parameter as nullable.
src/main/java/org/threeten/extra/Years.java Annotates equals(...) parameter as nullable.
src/main/java/org/threeten/extra/YearQuarter.java Annotates isSupported(...) parameters and equals(...) parameter as nullable.
src/main/java/org/threeten/extra/YearHalf.java Annotates isSupported(...) parameters and equals(...) parameter as nullable.
src/main/java/org/threeten/extra/Weeks.java Annotates equals(...) parameter as nullable.
src/main/java/org/threeten/extra/TemporalFields.java Marks resolve(...) return type as nullable.
src/main/java/org/threeten/extra/Seconds.java Annotates equals(...) parameter as nullable.
src/main/java/org/threeten/extra/scale/UtcInstant.java Marks cached toString field and equals(...) parameter as nullable.
src/main/java/org/threeten/extra/scale/TaiInstant.java Annotates equals(...) parameter as nullable.
src/main/java/org/threeten/extra/scale/package-info.java Applies package-level @NullMarked for scale package.
src/main/java/org/threeten/extra/Quarter.java Annotates isSupported(...) parameter as nullable.
src/main/java/org/threeten/extra/PeriodDuration.java Annotates equals(...) parameter as nullable.
src/main/java/org/threeten/extra/PackedFields.java Marks resolve(...) return types as nullable.
src/main/java/org/threeten/extra/package-info.java Applies package-level @NullMarked for main package.
src/main/java/org/threeten/extra/OffsetDate.java Annotates isSupported(...) parameters and equals(...) parameter as nullable.
src/main/java/org/threeten/extra/MutableClock.java Annotates equals(...) parameter as nullable.
src/main/java/org/threeten/extra/Months.java Annotates equals(...) parameter as nullable.
src/main/java/org/threeten/extra/Minutes.java Annotates equals(...) parameter as nullable.
src/main/java/org/threeten/extra/LocalDateRange.java Marks spliterator state and comparator return as nullable; annotates equals(...) parameter.
src/main/java/org/threeten/extra/Interval.java Annotates equals(...) parameter as nullable.
src/main/java/org/threeten/extra/Hours.java Annotates equals(...) parameter as nullable.
src/main/java/org/threeten/extra/HourMinute.java Annotates isSupported(...) parameters and equals(...) parameter as nullable.
src/main/java/org/threeten/extra/Half.java Annotates isSupported(...) parameter as nullable.
src/main/java/org/threeten/extra/Days.java Annotates equals(...) parameter as nullable.
src/main/java/org/threeten/extra/DayOfYear.java Annotates isSupported(...) and equals(...) parameters as nullable.
src/main/java/org/threeten/extra/DayOfMonth.java Annotates isSupported(...), isValidYearMonth(...), and equals(...) parameters as nullable.
src/main/java/org/threeten/extra/chrono/Symmetry454Chronology.java Marks getCalendarType() return as nullable.
src/main/java/org/threeten/extra/chrono/Symmetry010Chronology.java Marks getCalendarType() return as nullable.
src/main/java/org/threeten/extra/chrono/package-info.java Applies package-level @NullMarked for chrono package.
src/main/java/org/threeten/extra/chrono/InternationalFixedChronology.java Marks getCalendarType() return as nullable.
src/main/java/org/threeten/extra/chrono/BritishCutoverDate.java Annotates equals(...) parameter as nullable.
src/main/java/org/threeten/extra/chrono/BritishCutoverChronology.java Marks getCalendarType() return as nullable.
src/main/java/org/threeten/extra/chrono/AccountingDate.java Annotates equals(...) parameter as nullable.
src/main/java/org/threeten/extra/chrono/AccountingChronologyBuilder.java Marks builder fields as nullable where unset until configured.
src/main/java/org/threeten/extra/chrono/AccountingChronology.java Marks getCalendarType() return and equals(...) parameter as nullable.
src/main/java/org/threeten/extra/chrono/AbstractDate.java Annotates equals(...) parameter as nullable.
src/main/java/org/threeten/extra/AmPm.java Annotates isSupported(...) parameter as nullable.
src/main/java/module-info.java Adds requires static org.jspecify; for JPMS builds.
pom.xml Adds optional JSpecify dependency and version property.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/main/java/org/threeten/extra/chrono/AbstractDate.java

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pom.xml`:
- Around line 508-514: Remove the optional=true attribute and the copied
"mandatory in Scala" comment from the JSpecify dependency entry so the
org.jspecify:jspecify artifact is not marked optional; update the dependency
block that contains groupId org.jspecify and artifactId jspecify to omit the
<optional> element (and delete the inline comment) so downstream consumers can
access the annotations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c458efe7-9b78-4a7e-abb8-c44fb52c9bd9

📥 Commits

Reviewing files that changed from the base of the PR and between d938ae3 and 254d63e.

📒 Files selected for processing (39)
  • pom.xml
  • src/main/java/module-info.java
  • src/main/java/org/threeten/extra/AmPm.java
  • src/main/java/org/threeten/extra/DayOfMonth.java
  • src/main/java/org/threeten/extra/DayOfYear.java
  • src/main/java/org/threeten/extra/Days.java
  • src/main/java/org/threeten/extra/Half.java
  • src/main/java/org/threeten/extra/HourMinute.java
  • src/main/java/org/threeten/extra/Hours.java
  • src/main/java/org/threeten/extra/Interval.java
  • src/main/java/org/threeten/extra/LocalDateRange.java
  • src/main/java/org/threeten/extra/Minutes.java
  • src/main/java/org/threeten/extra/Months.java
  • src/main/java/org/threeten/extra/MutableClock.java
  • src/main/java/org/threeten/extra/OffsetDate.java
  • src/main/java/org/threeten/extra/PackedFields.java
  • src/main/java/org/threeten/extra/PeriodDuration.java
  • src/main/java/org/threeten/extra/Quarter.java
  • src/main/java/org/threeten/extra/Seconds.java
  • src/main/java/org/threeten/extra/TemporalFields.java
  • src/main/java/org/threeten/extra/Weeks.java
  • src/main/java/org/threeten/extra/YearHalf.java
  • src/main/java/org/threeten/extra/YearQuarter.java
  • src/main/java/org/threeten/extra/YearWeek.java
  • src/main/java/org/threeten/extra/Years.java
  • src/main/java/org/threeten/extra/chrono/AbstractDate.java
  • src/main/java/org/threeten/extra/chrono/AccountingChronology.java
  • src/main/java/org/threeten/extra/chrono/AccountingChronologyBuilder.java
  • src/main/java/org/threeten/extra/chrono/AccountingDate.java
  • src/main/java/org/threeten/extra/chrono/BritishCutoverChronology.java
  • src/main/java/org/threeten/extra/chrono/BritishCutoverDate.java
  • src/main/java/org/threeten/extra/chrono/InternationalFixedChronology.java
  • src/main/java/org/threeten/extra/chrono/Symmetry010Chronology.java
  • src/main/java/org/threeten/extra/chrono/Symmetry454Chronology.java
  • src/main/java/org/threeten/extra/chrono/package-info.java
  • src/main/java/org/threeten/extra/package-info.java
  • src/main/java/org/threeten/extra/scale/TaiInstant.java
  • src/main/java/org/threeten/extra/scale/UtcInstant.java
  • src/main/java/org/threeten/extra/scale/package-info.java

Comment thread pom.xml
@jodastephen

Copy link
Copy Markdown
Member Author

Unfortunately, JSpecify has no firm conclusion as to what library authors are supposed to do with the module-info.java and pom.xml definitions. Each project adopting JSpecify is making its own choice, and this is non-ideal.

My choice is to use requires static in module-info, and optional in pom.xml. This marks JSpecify (an annotation-only jar file) as fully optional. This is the same decision I took wrt joda-convert, and for the same reasons. Java code is supposed to work fully, even if an a annotation is missing. Any code (including that in the JDK) that can't handle a missing annotation class file is IMO buggy.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/java/org/threeten/extra/chrono/AccountingChronologyBuilder.java`:
- Around line 193-196: The builder's toChronology() currently uses
Objects.requireNonNull on endsOn, end, and division which throws
NullPointerException, violating the method's documented DateTimeException
contract; update toChronology() so it checks the same fields (endsOn, end,
division, and any other required state like
inLastWeek/leapWeekInMonth/yearOffset if applicable) and throws
java.time.DateTimeException with clear messages when a required field is unset,
then call AccountingChronology.create(...) only after all validations pass to
preserve the documented exception behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 72898300-eb70-418b-8563-cac201320160

📥 Commits

Reviewing files that changed from the base of the PR and between af59da9 and 0620ed1.

📒 Files selected for processing (8)
  • src/main/java/org/threeten/extra/OffsetDate.java
  • src/main/java/org/threeten/extra/PeriodDuration.java
  • src/main/java/org/threeten/extra/YearHalf.java
  • src/main/java/org/threeten/extra/YearQuarter.java
  • src/main/java/org/threeten/extra/chrono/AccountingChronology.java
  • src/main/java/org/threeten/extra/chrono/AccountingChronologyBuilder.java
  • src/main/java/org/threeten/extra/chrono/AccountingDate.java
  • src/main/java/org/threeten/extra/chrono/BritishCutoverDate.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/org/threeten/extra/chrono/AccountingChronology.java

Comment thread src/main/java/org/threeten/extra/chrono/AccountingChronologyBuilder.java Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants