Why a New CTO Banned java.util.Date and Demanded Immediate Refactoring

The article explains the fundamental design flaws of java.util.Date, why a code‑scan rule forces its removal, and provides a step‑by‑step guide to replace Date and related utilities with the modern java.time API, including concrete code transformations and migration tips.

Java Architect Handbook
Java Architect Handbook
Java Architect Handbook
Why a New CTO Banned java.util.Date and Demanded Immediate Refactoring

1. What’s wrong with java.util.Date?

java.util.Date is considered a bad type because most of its functionality was deprecated in Java 1.1. The author lists several design defects:

Misleading name: it represents an instant, not a date, and should be called Instant, similar to its java.time counterpart.

Non‑final class: encourages unsafe inheritance, e.g., java.sql.Date, which adds confusion.

Mutable: mutable instances (e.g., via setTime) force developers to create defensive copies.

Implicit system time‑zone usage: methods like toString() use the local zone, confusing many developers.

Zero‑based month numbering: inherited from C, leading to off‑by‑one errors.

Year offset from 1900: also a legacy from C, harming readability.

Unclear method names: getDate() returns the day of month, while getDay() returns the day of week.

Leap‑second handling is vague: the API mentions seconds 0‑61, but most developers assume 0‑59.

Lenient parsing: dates like "January 32" are accepted and rolled over to February 1, which is rarely useful.

2. Why must we change it?

The project's defect‑scan rule marks any use of java.util.Date or java.sql.Date as a mandatory defect; the build will fail unless the code is refactored. This rule forces the migration.

3. How to replace Date?

Replace java.util.Date and java.sql.Date with appropriate java.time types:

Use Instant for a timestamp (time‑zone‑independent).

Use LocalDateTime for date‑and‑time without a zone.

Use LocalDate for a pure date.

Use LocalTime for a pure time.

Use ZonedDateTime when a time‑zone is required.

Typical replacements:

Instant nowInstant = Instant.now();
LocalDateTime nowLocalDateTime = LocalDateTime.now();
ZonedDateTime nowZonedDateTime = ZonedDateTime.now();
Date nowFromDateInstant = Date.from(nowInstant); // if legacy API still needs Date
java.sql.Timestamp nowFromInstant = java.sql.Timestamp.from(nowInstant);

Key notes: Instant is the direct analogue of the old Date (time‑zone‑agnostic). LocalDateTime lacks zone information; converting to a timestamp requires attaching a zone (e.g., via ZonedDateTime). ZonedDateTime behaves like Calendar because it also carries zone data.

When converting back to java.util.Date, use Date.from(Instant).

4. Refactor the existing DateUtil methods

4.1 Replace new Date() and Calendar.getInstance().getTime()

// Old code
Date nowDate = new Date();
Date nowCalendarDate = Calendar.getInstance().getTime();

// New code using java.time
Instant nowInstant = Instant.now();
LocalDateTime nowLocalDateTime = LocalDateTime.now();
ZonedDateTime nowZonedDateTime = ZonedDateTime.now();

4.2 Rewrite utility methods

a. dateFormat

// Old version
public static String dateFormat(Date date, String dateFormat) {
    SimpleDateFormat formatter = new SimpleDateFormat(dateFormat);
    return formatter.format(date);
}

// New version
public static String dateFormat(LocalDateTime date, String dateFormat) {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat);
    return date.format(formatter);
}

b. Add/subtract helpers (second, minute, hour, day, month, year)

// Old version (example for seconds)
public static Date addSecond(Date date, int second) {
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    calendar.add(13, second);
    return calendar.getTime();
}

// New version
public static LocalDateTime addSecond(LocalDateTime date, int second) {
    return date.plusSeconds(second);
}

Similar replacements are provided for addMinute, addHour, addDay, addMonth, and addYear, each using the corresponding plusXxx method.

c. dateToWeek

// Old version (returns Chinese weekday string)
public static final String[] WEEK_DAY_OF_CHINESE = new String[]{"周日","周一","周二","周三","周四","周五","周六"};
public static String dateToWeek(Date date) {
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);
    return WEEK_DAY_OF_CHINESE[cal.get(7) - 1];
}

// New version using java.time
public static String dateToWeek(LocalDate date) {
    DayOfWeek dayOfWeek = date.getDayOfWeek();
    return WEEK_DAY_OF_CHINESE[dayOfWeek.getValue() % 7];
}

d. getStartOfDay / getEndOfDay

// Old version (returns java.util.Date)
public static Date getStartTimeOfDay(Date date) {
    if (date == null) return null;
    LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), ZoneId.systemDefault());
    LocalDateTime startOfDay = localDateTime.with(LocalTime.MIN);
    return Date.from(startOfDay.atZone(ZoneId.systemDefault()).toInstant());
}

public static Date getEndTimeOfDay(Date date) {
    if (date == null) return null;
    LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), ZoneId.systemDefault());
    LocalDateTime endOfDay = localDateTime.with(LocalTime.MAX);
    return Date.from(endOfDay.atZone(ZoneId.systemDefault()).toInstant());
}

// New version returning LocalDateTime
public static LocalDateTime getStartTimeOfDay(LocalDateTime date) {
    if (date == null) return null;
    return date.toLocalDate().atStartOfDay();
}

public static LocalDateTime getEndTimeOfDay(LocalDateTime date) {
    if (date == null) return null;
    return date.toLocalDate().atTime(LocalTime.MAX);
}

e. betweenStartAndEnd

// Old version using Date
public static Boolean betweenStartAndEnd(Date nowTime, Date beginTime, Date endTime) {
    Calendar date = Calendar.getInstance();
    date.setTime(nowTime);
    Calendar begin = Calendar.getInstance();
    begin.setTime(beginTime);
    Calendar end = Calendar.getInstance();
    end.setTime(endTime);
    return date.after(begin) && date.before(end);
}

// New version using Instant
public static Boolean betweenStartAndEnd(Instant nowTime, Instant beginTime, Instant endTime) {
    return nowTime.isAfter(beginTime) && nowTime.isBefore(endTime);
}

5. Summary

The migration is conceptually straightforward but highly risky: a single missed replacement can cause interface errors or even prevent the application from starting, consuming a lot of effort. The author advises careful, systematic refactoring and testing.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

backendJavarefactoringjava.timejava.util.Datedate-time API
Java Architect Handbook
Written by

Java Architect Handbook

Focused on Java interview questions and practical article sharing, covering algorithms, databases, Spring Boot, microservices, high concurrency, JVM, Docker containers, and ELK-related knowledge. Looking forward to progressing together with you.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.