Avoid Legacy Date/Time API

ID

java.avoid_legacy_datetime

Severity

low

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Best Practice

Language

Java

Tags

best-practice

Description

Reports usages of the legacy JDK date/time API — java.util.Date, java.util.Calendar (including Calendar.getInstance()) and java.util.GregorianCalendar — in favour of the modern java.time package (JSR-310).

Rationale

java.util.Date (since JDK 1.0), java.util.Calendar (since JDK 1.1) and its subclass java.util.GregorianCalendar are mutable, not thread-safe, and expose a confusing API (zero-based months, year offset from 1900 for Date). Their factories and constructors also perform a TimeZone/Locale lookup on every call, which is comparatively expensive.

Java 8 introduced java.time: an immutable, thread-safe and significantly more expressive date/time API. Code creating new Date, Calendar or GregorianCalendar instances today should be considered legacy and replaced.

// Bad — legacy, mutable, confusing API
Date now = new Date();
Calendar c = Calendar.getInstance();
GregorianCalendar g = new GregorianCalendar(2026, Calendar.JANUARY, 1); // month is zero-based

Remediation

Use the appropriate java.time type:

  • java.time.Instant — a point on the timeline (UTC).

  • java.time.LocalDate / LocalTime / LocalDateTime — date/time without zone.

  • java.time.ZonedDateTime / OffsetDateTime — date/time with zone or offset.

  • java.time.Clock — when an injectable time source is needed.

// Good — immutable, thread-safe, expressive
Instant now = Instant.now();
LocalDate date = LocalDate.of(2026, 1, 1); // month is one-based
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("UTC"));

When interoperating with legacy APIs that still require Date/Calendar, convert at the boundary (Date.from(instant), GregorianCalendar.from(zdt)).