Skip to content

Add JSpecify null annotations#379

Merged
jodastephen merged 6 commits into
mainfrom
jspecify
Jun 14, 2026
Merged

Add JSpecify null annotations#379
jodastephen merged 6 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 nullability annotation dependency and updated module metadata.
    • Marked the package (and many public APIs) with nullness contracts to better reflect nullable behavior.
    • Strengthened deserialization checks to fail fast when critical fields are missing.
  • Tests

    • Added parameterized tests for chronology builder methods to verify null inputs now raise NullPointerException.
  • Documentation

    • Updated release notes for 1.10.0 (SNAPSHOT) describing the null-input exception behavior change.

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

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 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 non-null checks in deserialization and builder methods with corresponding test updates.

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 core temporal types
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/Half.java, src/main/java/org/threeten/extra/HourMinute.java, src/main/java/org/threeten/extra/Days.java, src/main/java/org/threeten/extra/Hours.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/Years.java, src/main/java/org/threeten/extra/Interval.java, src/main/java/org/threeten/extra/LocalDateRange.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
Public API signatures updated with @Nullable on parameters, equals overrides, and query generic bounds; internal fields marked @Nullable where applicable; readResolve checks added in OffsetDate and PeriodDuration.
Accounting chronology validation with non-null enforcement
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/test/java/org/threeten/extra/chrono/TestAccountingChronologyBuilder.java, src/changes/changes.xml
Factory create(...) and readResolve() methods now enforce non-null endsOn, end, and division via Objects.requireNonNull; builder setters validate inputs; field nullability annotated; tests updated to assert NullPointerException on null inputs instead of previous behavior; release notes updated.
Nullability annotations on chronology family types
src/main/java/org/threeten/extra/chrono/AbstractDate.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/CopticChronology.java, src/main/java/org/threeten/extra/chrono/DiscordianChronology.java, src/main/java/org/threeten/extra/chrono/EthiopicChronology.java, src/main/java/org/threeten/extra/chrono/JulianChronology.java, src/main/java/org/threeten/extra/chrono/PaxChronology.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
resolveDate(...) overrides annotated to return @Nullable; getCalendarType() annotated as returning @Nullable String; equals parameters annotated @Nullable; query generic bounds updated; deserialization checks added in BritishCutoverDate and YearHalf/YearQuarter.
Nullability annotations on instant/scale types
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
Equals parameters and cached fields annotated @Nullable; package-level @NullMarked applied.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • ThreeTen/threeten-extra#376: Changes to query(TemporalQuery) signatures and nullable query return handling in several of the same temporal types.

Poem

🐰 I hopped through code with tiny paws,
Marked nulls where silence once did pause,
A tag, a check, a property set,
Now signatures hum a safer duet,
Hooray — nullable, neat applause!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately summarizes the main change: adding JSpecify nullness annotations throughout the codebase.
Docstring Coverage ✅ Passed Docstring coverage is 91.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.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 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
@jodastephen jodastephen mentioned this pull request Jun 10, 2026

@cpovirk cpovirk 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.

I checked this against some partial automation that we have for annotating for nullness. Aside from a couple cases in which the automation got unusual cases wrong, all its annotations match yours. I looked at the new requireNonNull calls, and they all look reasonable to me. Then I grepped around a bit in the files you edited and in the partial JSpecify coverage we have for java.time, and then I turned Gemini loose.

I'm kicking off some testing in our depot to run overnight.

I offer a very optional comment below as a little evidence that I did in fact look through some code myself :)

Comment thread src/main/java/org/threeten/extra/chrono/BritishCutoverChronology.java Outdated
Comment thread src/main/java/org/threeten/extra/Interval.java Outdated
Comment thread src/main/java/org/threeten/extra/LocalDateRange.java Outdated
Comment thread src/main/java/org/threeten/extra/AmPm.java Outdated

@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: 2

♻️ Duplicate comments (1)
src/main/java/org/threeten/extra/chrono/AccountingChronologyBuilder.java (1)

192-195: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

toChronology() throws NullPointerException but documents DateTimeException.

Line 190 declares @throws DateTimeException if the chronology cannot be built, yet when endsOn, end, or division are null, AccountingChronology.create(...) (lines 177-179 in AccountingChronology.java) throws NullPointerException via Objects.requireNonNull. This breaks the documented contract and may break callers that catch only DateTimeException. Validate required builder fields and throw DateTimeException with a descriptive message before delegating to create(...).

🛠️ Proposed fix
 public AccountingChronology toChronology() {
-    //noinspection DataFlowIssue - nullness checked in the constructor of AccountingChronology
+    if (endsOn == null || end == null || division == null) {
+        throw new DateTimeException(
+                "AccountingChronologyBuilder is incomplete: endsOn, end, and division must be set");
+    }
     return AccountingChronology.create(endsOn, end, inLastWeek, division, leapWeekInMonth, yearOffset);
 }
🤖 Prompt for 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.

In `@src/main/java/org/threeten/extra/chrono/AccountingChronologyBuilder.java`
around lines 192 - 195, The toChronology() method currently delegates to
AccountingChronology.create(...) which can throw NullPointerException when
required fields are null; update toChronology() to validate the required builder
fields (endsOn, end, division and any other non-null parameters used by
AccountingChronology.create) and, if any are null, throw a
java.time.DateTimeException with a clear descriptive message listing the missing
field(s) before calling AccountingChronology.create(...), preserving the
documented contract.
🧹 Nitpick comments (1)
src/main/java/org/threeten/extra/chrono/AccountingChronology.java (1)

242-246: ⚡ Quick win

Inconsistent null-checking in readResolve().

Lines 243-244 explicitly check endsOn and end with Objects.requireNonNull, but division (obtained via getDivision() on line 245) is not checked here. Since AccountingChronology.create(...) validates all three on lines 177-179, the early checks for endsOn and end provide no additional safety—they only make the code inconsistent. Either check all required fields early in readResolve(), or remove the redundant checks and rely on create(...) to enforce non-null requirements uniformly.

🔧 Option 1: Check all fields consistently
 private Object readResolve() {
     Objects.requireNonNull(endsOn, "endsOn");
     Objects.requireNonNull(end, "end");
+    Objects.requireNonNull(division, "division");
     return AccountingChronology.create(endsOn, end, inLastWeek, getDivision(), leapWeekInMonth, yearOffset);
 }
🔧 Option 2: Remove redundant checks (preferred)
 private Object readResolve() {
-    Objects.requireNonNull(endsOn, "endsOn");
-    Objects.requireNonNull(end, "end");
     return AccountingChronology.create(endsOn, end, inLastWeek, getDivision(), leapWeekInMonth, yearOffset);
 }
🤖 Prompt for 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.

In `@src/main/java/org/threeten/extra/chrono/AccountingChronology.java` around
lines 242 - 246, Remove the redundant early null-checks in
AccountingChronology.readResolve(): delete the Objects.requireNonNull(endsOn,
"endsOn") and Objects.requireNonNull(end, "end") calls and rely on
AccountingChronology.create(endsOn, end, inLastWeek, getDivision(),
leapWeekInMonth, yearOffset) to validate endsOn, end and division uniformly
(getDivision() is already passed into create); this makes null-checking
consistent and avoids duplicate validation.
🤖 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/changes/changes.xml`:
- Around line 10-16: The release note incorrectly states the exception change;
update the release XML entry so it references that adding JSpecify nullness
annotations causes builder validation in
AccountingChronologyBuilder.toChronology() to throw NullPointerException (or a
null-related runtime exception) which breaks the documented DateTimeException
contract for AccountingChronology construction, rather than claiming a swap from
IllegalStateException; mention the affected symbol AccountingChronology and the
builder method toChronology() and clarify this impacts the documented
DateTimeException behavior.

In
`@src/test/java/org/threeten/extra/chrono/TestAccountingChronologyBuilder.java`:
- Around line 462-485: The tests currently assert NullPointerException for
incomplete builders (see test_nullPassedIn_nearestEndOf and
test_nullPassedIn_inLastWeekOf) but toChronology() in
AccountingChronologyBuilder is documented to throw DateTimeException; update the
JavaDoc on AccountingChronologyBuilder#toChronology to also declare (and
document the conditions for) NullPointerException when required builder
parameters (e.g., values set via endsOn, nearestEndOf, inLastWeekOf,
withDivision, leapWeekInMonth) are null or incomplete so the documentation
matches the observed behavior and the test expectations.

---

Duplicate comments:
In `@src/main/java/org/threeten/extra/chrono/AccountingChronologyBuilder.java`:
- Around line 192-195: The toChronology() method currently delegates to
AccountingChronology.create(...) which can throw NullPointerException when
required fields are null; update toChronology() to validate the required builder
fields (endsOn, end, division and any other non-null parameters used by
AccountingChronology.create) and, if any are null, throw a
java.time.DateTimeException with a clear descriptive message listing the missing
field(s) before calling AccountingChronology.create(...), preserving the
documented contract.

---

Nitpick comments:
In `@src/main/java/org/threeten/extra/chrono/AccountingChronology.java`:
- Around line 242-246: Remove the redundant early null-checks in
AccountingChronology.readResolve(): delete the Objects.requireNonNull(endsOn,
"endsOn") and Objects.requireNonNull(end, "end") calls and rely on
AccountingChronology.create(endsOn, end, inLastWeek, getDivision(),
leapWeekInMonth, yearOffset) to validate endsOn, end and division uniformly
(getDivision() is already passed into create); this makes null-checking
consistent and avoids duplicate validation.
🪄 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: e5568b87-ed85-4549-821e-50f6a186656a

📥 Commits

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

📒 Files selected for processing (23)
  • src/changes/changes.xml
  • 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/Half.java
  • src/main/java/org/threeten/extra/HourMinute.java
  • src/main/java/org/threeten/extra/Interval.java
  • src/main/java/org/threeten/extra/LocalDateRange.java
  • src/main/java/org/threeten/extra/OffsetDate.java
  • src/main/java/org/threeten/extra/Quarter.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/chrono/AccountingChronology.java
  • src/main/java/org/threeten/extra/chrono/AccountingChronologyBuilder.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/CopticChronology.java
  • src/main/java/org/threeten/extra/chrono/DiscordianChronology.java
  • src/main/java/org/threeten/extra/chrono/EthiopicChronology.java
  • src/main/java/org/threeten/extra/chrono/JulianChronology.java
  • src/main/java/org/threeten/extra/chrono/PaxChronology.java
  • src/test/java/org/threeten/extra/chrono/TestAccountingChronologyBuilder.java
✅ Files skipped from review due to trivial changes (3)
  • src/main/java/org/threeten/extra/chrono/DiscordianChronology.java
  • src/main/java/org/threeten/extra/DayOfYear.java
  • src/main/java/org/threeten/extra/chrono/CopticChronology.java
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/main/java/org/threeten/extra/chrono/BritishCutoverChronology.java
  • src/main/java/org/threeten/extra/Interval.java
  • src/main/java/org/threeten/extra/chrono/BritishCutoverDate.java
  • src/main/java/org/threeten/extra/OffsetDate.java
  • src/main/java/org/threeten/extra/HourMinute.java
  • src/main/java/org/threeten/extra/DayOfMonth.java

Comment thread src/changes/changes.xml
Comment on lines +10 to +16
<release version="1.10.0" date="SNAPSHOT" description="v1.10.0">
<action dev="jodastephen" type="add" issue="379">
Add JSpecify nullness annotations.
This causes AccountingChronology to throw NullPointerException instead of IllegalStateException when null is passed in.
See also #170.
</action>
</release>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Release note inaccurately describes the exception change.

Line 13 states: "This causes AccountingChronology to throw NullPointerException instead of IllegalStateException when null is passed in." However, the builder's toChronology() method (line 190 in AccountingChronologyBuilder.java) documents @throws DateTimeException, not IllegalStateException. The note should clarify that the change affects builder validation and breaks the documented DateTimeException contract.

📝 Suggested correction
       <action dev="jodastephen" type="add" issue="379">
         Add JSpecify nullness annotations.
-        This causes AccountingChronology to throw NullPointerException instead of IllegalStateException when null is passed in.
+        This causes AccountingChronologyBuilder.toChronology() to throw NullPointerException instead of the documented DateTimeException when required fields are null.
         See also `#170`.
       </action>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<release version="1.10.0" date="SNAPSHOT" description="v1.10.0">
<action dev="jodastephen" type="add" issue="379">
Add JSpecify nullness annotations.
This causes AccountingChronology to throw NullPointerException instead of IllegalStateException when null is passed in.
See also #170.
</action>
</release>
<release version="1.10.0" date="SNAPSHOT" description="v1.10.0">
<action dev="jodastephen" type="add" issue="379">
Add JSpecify nullness annotations.
This causes AccountingChronologyBuilder.toChronology() to throw NullPointerException instead of the documented DateTimeException when required fields are null.
See also `#170`.
</action>
</release>
🤖 Prompt for 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.

In `@src/changes/changes.xml` around lines 10 - 16, The release note incorrectly
states the exception change; update the release XML entry so it references that
adding JSpecify nullness annotations causes builder validation in
AccountingChronologyBuilder.toChronology() to throw NullPointerException (or a
null-related runtime exception) which breaks the documented DateTimeException
contract for AccountingChronology construction, rather than claiming a swap from
IllegalStateException; mention the affected symbol AccountingChronology and the
builder method toChronology() and clarify this impacts the documented
DateTimeException behavior.

Comment on lines +462 to +485
public static Stream<Arguments> data_nullPassedIn() {
return Stream.of(
arguments(DayOfWeek.MONDAY, Month.JANUARY, null, 13),
arguments(DayOfWeek.MONDAY, null, AccountingYearDivision.THIRTEEN_EVEN_MONTHS_OF_4_WEEKS, 13),
arguments(null, Month.JANUARY, AccountingYearDivision.THIRTEEN_EVEN_MONTHS_OF_4_WEEKS, 13));
}

@ParameterizedTest
@MethodSource("data_nullPassedIn")
public void test_nullPassedIn_nearestEndOf(DayOfWeek dayOfWeek, Month ending, AccountingYearDivision division, int leapWeekInMonth) {
assertThrows(NullPointerException.class,
() -> new AccountingChronologyBuilder().endsOn(dayOfWeek).nearestEndOf(ending)
.withDivision(division).leapWeekInMonth(leapWeekInMonth)
.toChronology());
}

@ParameterizedTest
@MethodSource("data_nullPassedIn")
public void test_nullPassedIn_inLastWeekOf(DayOfWeek dayOfWeek, Month ending, AccountingYearDivision division, int leapWeekInMonth) {
assertThrows(NullPointerException.class,
() -> new AccountingChronologyBuilder().endsOn(dayOfWeek).inLastWeekOf(ending)
.withDivision(division).leapWeekInMonth(leapWeekInMonth)
.toChronology());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Tests confirm NullPointerException but toChronology() documents DateTimeException.

The new test_nullPassedIn_* methods correctly verify that NullPointerException is thrown for null builder inputs. However, this contradicts the @throws DateTimeException contract documented for toChronology() at line 190 in AccountingChronologyBuilder.java. Either the tests should expect DateTimeException, or the JavaDoc contract must be updated to declare NullPointerException for incomplete builders (which would be a breaking API change).

🤖 Prompt for 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.

In `@src/test/java/org/threeten/extra/chrono/TestAccountingChronologyBuilder.java`
around lines 462 - 485, The tests currently assert NullPointerException for
incomplete builders (see test_nullPassedIn_nearestEndOf and
test_nullPassedIn_inLastWeekOf) but toChronology() in
AccountingChronologyBuilder is documented to throw DateTimeException; update the
JavaDoc on AccountingChronologyBuilder#toChronology to also declare (and
document the conditions for) NullPointerException when required builder
parameters (e.g., values set via endsOn, nearestEndOf, inLastWeekOf,
withDivision, leapWeekInMonth) are null or incomplete so the documentation
matches the observed behavior and the test expectations.

@cpovirk cpovirk 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.

(The overnight testing all went well, too, so I think we'll be able to import this very soon after it lands.)

cpovirk added a commit to jspecify/jdk that referenced this pull request Jun 12, 2026

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

♻️ Duplicate comments (1)
src/main/java/org/threeten/extra/chrono/AccountingChronologyBuilder.java (1)

184-190: ⚠️ Potential issue | 🟠 Major

JavaDoc-behavior mismatch for exception contract.

The JavaDoc (lines 184-186) documents IllegalStateException for invalid required fields, but the actual behavior throws NullPointerException when endsOn, end, or division are null (via Objects.requireNonNull in AccountingChronology.create, see context snippet 1). The tests (context snippet 3) expect NullPointerException.

Additionally, the nullness suppression comment (line 189) says "constructor" but the null checks occur in the static factory method AccountingChronology.create, not the private constructor.

🤖 Prompt for 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.

In `@src/main/java/org/threeten/extra/chrono/AccountingChronologyBuilder.java`
around lines 184 - 190, The JavaDoc for the toChronology method documents
IllegalStateException for invalid required fields, but the actual implementation
throws NullPointerException when endsOn, end, or division are null (via
Objects.requireNonNull in AccountingChronology.create). Update the `@throws`
clause in the JavaDoc to document NullPointerException instead of
IllegalStateException to match the actual behavior. Additionally, fix the
nullness suppression comment on line 189 to correctly reference
AccountingChronology.create static factory method instead of "constructor",
since that is where the null checks actually occur.
🤖 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.

Duplicate comments:
In `@src/main/java/org/threeten/extra/chrono/AccountingChronologyBuilder.java`:
- Around line 184-190: The JavaDoc for the toChronology method documents
IllegalStateException for invalid required fields, but the actual implementation
throws NullPointerException when endsOn, end, or division are null (via
Objects.requireNonNull in AccountingChronology.create). Update the `@throws`
clause in the JavaDoc to document NullPointerException instead of
IllegalStateException to match the actual behavior. Additionally, fix the
nullness suppression comment on line 189 to correctly reference
AccountingChronology.create static factory method instead of "constructor",
since that is where the null checks actually occur.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c94b9fff-7405-4284-90aa-17e66bddc36b

📥 Commits

Reviewing files that changed from the base of the PR and between 0cd8646 and 6c4b320.

📒 Files selected for processing (1)
  • src/main/java/org/threeten/extra/chrono/AccountingChronologyBuilder.java

@jodastephen jodastephen merged commit 1004671 into main Jun 14, 2026
9 checks passed
@jodastephen jodastephen deleted the jspecify branch June 14, 2026 22:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

3 participants