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