Backend Development 13 min read

Why java.util.Date Should Be Replaced and How to Migrate to java.time

The article explains the many design flaws of java.util.Date, why modern Java projects must abandon it, and provides a step‑by‑step guide with code examples for migrating to the immutable java.time API such as Instant, LocalDateTime and ZonedDateTime.

Architecture Digest
Architecture Digest
Architecture Digest
Why java.util.Date Should Be Replaced and How to Migrate to java.time

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

java.util.Date (Date from now on) is a badly designed type; most of its members were deprecated in Java 1.1 yet remain in use.

Design defects include:

Misleading name: it represents an instant in time, not a calendar date. It should be called Instant , just like its java.time counterpart.

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

Mutable: mutable date/time objects force developers to create defensive copies.

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

Month numbers start at 0 (C‑style), leading to off‑by‑one errors.

Year is offset from 1900, another legacy C artifact.

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

Leap‑second handling is vague: the API suggests seconds can be 0‑61, but most code assumes 0‑59.

Lenient range checking: dates like “January 32” are accepted and rolled over to February 1, which is rarely useful.

Key reason: using java.util.Date leads to bugs, unclear code, and maintenance headaches.

2. Why change it?

Our static code analysis rule flags java.util.Date as a mandatory defect; the build will fail unless it is replaced.

3. How to change?

1. Compare database date fields with DO mappings

1) Determine field type

If the field stores both date and time, use LocalDateTime .

If it stores only a date, use LocalDate .

If it stores only a time, use LocalTime .

If it stores a timestamp with zone, use Instant or ZonedDateTime .

2) Update data‑object classes

Replace Date fields with the appropriate java.time type.

2. Refactor DateUtil methods

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

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

with the java.time equivalents:

// Using Instant to represent a point in time, similar to Date
Instant nowInstant = Instant.now();

// For a full date‑time without zone
LocalDateTime nowLocalDateTime = LocalDateTime.now();

// For zone‑aware operations
ZonedDateTime nowZonedDateTime = ZonedDateTime.now();

// Convert back to java.util.Date if legacy APIs still need it
Date nowFromDateInstant = Date.from(nowInstant);

// Convert to java.sql.Timestamp
java.sql.Timestamp nowFromInstant = java.sql.Timestamp.from(nowInstant);

Key notes:

Instant is zone‑independent and suitable for timestamps.

LocalDateTime lacks zone information; combine with ZonedDateTime when needed.

ZonedDateTime mirrors the old Calendar behavior.

Convert back to java.util.Date via Date.from(Instant) when required.

2) Refactor basic utility methods

a. dateFormat

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

becomes

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

b. addSecond / addMinute / addHour / addDay / addMonth / addYear

public static Date addSecond(Date date, int second) {
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    calendar.add(13, second);
    return calendar.getTime();
}
// ... similar for minute, hour, day, month, year

becomes

public static LocalDateTime addSecond(LocalDateTime date, int second) {
    return date.plusSeconds(second);
}
public static LocalDateTime addMinute(LocalDateTime date, int minute) {
    return date.plusMinutes(minute);
}
public static LocalDateTime addHour(LocalDateTime date, int hour) {
    return date.plusHours(hour);
}
public static LocalDateTime addDay(LocalDateTime date, int day) {
    return date.plusDays(day);
}
public static LocalDateTime addMonth(LocalDateTime date, int month) {
    return date.plusMonths(month);
}
public static LocalDateTime addYear(LocalDateTime date, int year) {
    return date.plusYears(year);
}

c. dateToWeek

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];
}

becomes

public static final String[] WEEK_DAY_OF_CHINESE = new String[]{"周日","周一","周二","周三","周四","周五","周六"};
public static String dateToWeek(LocalDate date) {
    DayOfWeek dayOfWeek = date.getDayOfWeek();
    return WEEK_DAY_OF_CHINESE[dayOfWeek.getValue() % 7];
}

d. getStartOfDay / getEndOfDay

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());
}

becomes

public static LocalDateTime getStartTimeOfDay(LocalDateTime date) {
    if (date == null) return null;
    // 00:00 of the same day
    return date.toLocalDate().atStartOfDay();
}
public static LocalDateTime getEndTimeOfDay(LocalDateTime date) {
    if (date == null) return null;
    // 23:59:59.999999999 of the same day
    return date.toLocalDate().atTime(LocalTime.MAX);
}

e. betweenStartAndEnd

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);
}

becomes

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

The migration touches every layer—from database columns to DTOs—so all related converters must be updated together to avoid runtime errors.

4. Summary

The refactor is not conceptually hard but is highly coupled; a single missed change can cause interface errors or application startup failures, demanding careful, comprehensive updates.

backendJavamigrationdate-timejava.timejava.util.Date
Architecture Digest
Written by

Architecture Digest

Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.

0 followers
Reader feedback

How this landed with the community

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