Why Does MyBatis Insert Yesterday’s Date? Uncovering the Timezone Trap

A colleague reported that a MyBatis insert stored a timestamp from the previous day, leading to a deep investigation that revealed mismatched timezone handling between MySQL, the JDBC driver, and Java, and presented two practical fixes to align all components.

Senior Brother's Insights
Senior Brother's Insights
Senior Brother's Insights
Why Does MyBatis Insert Yesterday’s Date? Uncovering the Timezone Trap

Problem Statement

Colleague reported that a MyBatis insert stored a date that was one day earlier. The initial guess was a MyBatis bug, but the investigation quickly pointed to a timezone mismatch.

Hypothesis

Possible causes considered: the database column type datetime, a type mismatch in the MyBatis‑generated code, or an incorrect column definition such as using varchar for dates.

Investigation Steps

Check the database column type – it is datetime.

Check the entity field type – it is java.util.Date.

Reproduce the bug with a unit test.

@SpringBootTest
class DateTimeTests {
    @Resource
    private UserMapper userMapper;

    @Test
    public void testDate() {
        User user = new User();
        // other fields omitted
        user.setCreateDate(new Date());
        userMapper.insertSelective(user);
    }
}

Running the test shows the inserted timestamp is 14 hours behind the current time, confirming a timezone issue.

Timezone Diagnosis

Server Time

[root@xxx ~]# date
2021年 11月 25日 星期四 09:26:25 CST
[root@xxx ~]# date -R
Thu, 25 Nov 2021 09:33:34 +0800

The server uses CST (+08:00), which is correct for China.

Database Timezone Variables

show variables like '%time_zone%';
+----------------------------+-------+
| Variable                   | Value |
+----------------------------+-------+
| system_time_zone           | CST   |
| time_zone                  | SYSTEM|
+----------------------------+-------+
system_time_zone

is the global system timezone (CST), and time_zone defaults to SYSTEM.

Java Timezone

@Test
public void testDate() {
    System.out.println(System.getProperty("user.timezone"));
    // rest of test
}

Output: Asia/Shanghai, i.e., Java sees UTC+8.

Driver Behaviour

Debugging MySQL Connector/J shows that the driver reads time_zone as SYSTEM, then falls back to system_time_zone which is "CST". The driver converts "CST" via TimeZone.getTimeZone("CST"), which Java interprets as US Central Standard Time (UTC‑6).

public void configureTimezone() {
    String configuredTimeZoneOnServer = this.serverSession.getServerVariable("time_zone");
    if ("SYSTEM".equalsIgnoreCase(configuredTimeZoneOnServer)) {
        configuredTimeZoneOnServer = this.serverSession.getServerVariable("system_time_zone");
    }
    String canonicalTimezone = getPropertySet().getStringProperty(PropertyKey.serverTimezone).getValue();
    if (configuredTimeZoneOnServer != null) {
        if (canonicalTimezone == null || canonicalTimezone.isEmpty()) {
            canonicalTimezone = TimeUtil.getCanonicalTimezone(configuredTimeZoneOnServer, getExceptionInterceptor());
        }
    }
    if (canonicalTimezone != null && !canonicalTimezone.isEmpty()) {
        this.serverSession.setServerTimeZone(TimeZone.getTimeZone(canonicalTimezone));
    }
}

The mapping file TimeZoneMapping.properties maps "CST" to "CST", which the JDK resolves to the US zone, causing a 14‑hour offset when the driver formats the timestamp.

Debug screenshot showing Calendar timezone set to Locale.US (CST -6)
Debug screenshot showing Calendar timezone set to Locale.US (CST -6)
Debug screenshot of serverVariables containing system_time_zone=CST
Debug screenshot of serverVariables containing system_time_zone=CST

Solutions

Option 1 – Adjust MySQL Timezone

# mysql -uroot -p
set global time_zone = '+8:00';
set time_zone = '+8:00';
flush privileges;

After changing, show variables like '%time_zone%' reports time_zone = +08:00 and the unit test passes.

Option 2 – Specify Timezone in JDBC URL

url: jdbc:mysql://host:3306/db_name?useUnicode=true&characterEncoding=utf8&autoReconnect=true&serverTimezone=Asia/Shanghai

Adding serverTimezone=Asia/Shanghai forces the driver to use the correct zone, also fixing the bug.

Root Cause Summary

MySQL system_time_zone is set to "CST".

Connector/J reads time_zone as "SYSTEM", then uses the value of system_time_zone.

Java’s TimeZone.getTimeZone("CST") returns US Central time, not China Standard Time.

The driver’s SqlTimestampValueFactory formats the timestamp with this wrong zone, producing a 14‑hour shift.

Key Takeaways

Check server, database, and JVM timezone settings when timestamps appear incorrect.

MySQL variables system_time_zone and time_zone can be ambiguous; prefer explicit UTC offsets or region IDs.

When using MySQL Connector/J, always set serverTimezone in the JDBC URL to avoid implicit "CST" mapping.

Unit tests are an effective way to reproduce and isolate timezone bugs.

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.

DebuggingJavaSpring BootmysqlMyBatisTimezone
Senior Brother's Insights
Written by

Senior Brother's Insights

A public account focused on workplace, career growth, team management, and self-improvement. The author is the writer of books including 'SpringBoot Technology Insider' and 'Drools 8 Rule Engine: Core Technology and Practice'.

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.