Adding one month to a date near month end can clamp the day, so repeated additions may differ from an addition anchored to the original date.
Java plusMonths at month end: prevent renewal-date drift
Keep the original recurrence rule
A contract signed on January 31 may intend the last day of every month. Repeatedly adding one month to the previous invoice date loses that intent after February clamps the day. Calculate each occurrence from the anchor month and a declared day policy instead.
The fixture shows January 31, 2024 plus one month becomes February 29; another plus one month becomes March 29. Directly adding two months to January 31 yields March 31. YearMonth.atEndOfMonth expresses an explicit last-day policy.
Make the policy testable
Some products want the 31st where it exists and the last day otherwise; others want the 29th after the first clamp. Either can be valid. The error is allowing the library's chained arithmetic to choose the policy without review.
Working program
import java.time.LocalDate;
import java.time.YearMonth;
public class ContractRenewalAnchor {
public static void main(String[] args) {
LocalDate signed = LocalDate.of(2024, 1, 31);
LocalDate chained = signed.plusMonths(1).plusMonths(1);
LocalDate anchored = signed.plusMonths(2);
LocalDate lastDayPolicy = YearMonth.from(signed).plusMonths(2).atEndOfMonth();
System.out.println(signed.plusMonths(1));
System.out.println(chained);
System.out.println(anchored);
System.out.println(lastDayPolicy);
}
}Output
2024-02-29
2024-03-29
2024-03-31
2024-03-31Cost and ownership
Each calculation uses fixed-size immutable dates. A billing scheduler should retain its anchor and recurrence rule rather than derive future dates solely from the last emitted date; otherwise a one-time clamp can permanently alter the sequence.
Common Mistakes
- Do not assume repeated plusMonths preserves the original day of month.
- Do not encode a last-day rule as an accidental side effect of clamping.
- Do not model a renewal month as a fixed number of elapsed hours.
Read next
Java YearMonth.atEndOfMonth: calculate a month boundary without guessed days, Java LocalDate half-open ranges: define an exclusive end, Java Duration versus Period across a daylight-saving change, Java date and time: local schedules versus instants.
