Backend Development 13 min read

Problems with java.util.Date and Migration to java.time API

The article explains the design flaws of java.util.Date, such as mutability, misleading naming, and timezone issues, and provides a step‑by‑step guide to replace it and related APIs with java.time classes like Instant, LocalDateTime, and ZonedDateTime, including code examples.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Problems with java.util.Date and Migration to java.time API

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

java.util.Date is a flawed type; most of its functionality was deprecated in Java 1.1 but it is still widely used.

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

Non‑final: It can be subclassed (e.g., java.sql.Date ), which adds confusion.

Mutable: Methods such as setTime make it mutable, forcing developers to create defensive copies.

Implicit system time‑zone in toString() : The default string representation uses the local time‑zone, confusing many developers.

Month index starts at 0: Copied from C, this causes off‑by‑one errors.

Year based on 1900: Also inherited from C, reducing readability.

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

Leap‑second handling is vague: The documentation says seconds can be 0‑61, but most code assumes 0‑59.

Lenient parsing: Dates like “January 32” are accepted and interpreted as February 1, which is rarely useful.

2. Why change it?

Our static‑analysis rule treats usage of java.util.Date as a defect that must be fixed before a release, so the code base needs to be updated.

3. How to migrate?

Replace java.util.Date and java.sql.Date with appropriate java.time classes such as Instant , LocalDateTime , LocalDate , LocalTime , or ZonedDateTime depending on the semantics of the field.

3.1 Update data‑object classes

Identify whether a field represents a date, a time, a date‑time, or a timestamp and change its type accordingly:

If it represents both date and time → LocalDateTime

If it represents only a date → LocalDate

If it represents only a time → LocalTime

If it represents a timestamp with zone → Instant or ZonedDateTime

3.2 Refactor DateUtil methods

Replace usages of new Date() and Calendar.getInstance().getTime() with the modern API.

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

After migration:

// Using Instant – represents a point in time, similar to the old Date
Instant nowInstant = Instant.now();

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

// When zone information is required
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);

3.3 Rewrite utility methods

a. dateFormat

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

Using java.time :

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 methods for minute, hour, day, month, year

After migration:

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

Using java.time :

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

After migration:

public static LocalDateTime getStartTimeOfDay(LocalDateTime date) {
    if (date == null) return null;
    // 00:00 of the given day
    return date.toLocalDate().atStartOfDay();
}

public static LocalDateTime getEndTimeOfDay(LocalDateTime date) {
    if (date == null) return null;
    // 23:59:59.999999999 of the given 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);
}

Using java.time :

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

4. Summary

Although the migration is conceptually simple, it touches many layers of the code base; missing a single change can cause compilation errors or runtime failures, so careful, comprehensive refactoring is required.

5. Call to action

If this guide helped you, consider liking, sharing, or following the author’s technical community for more deep‑dive articles.

backendJavaMigrationAPIDatejava.time
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn

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.