Databases 17 min read

Why MySQL JDBC Driver Misinterprets CST and How to Fix the 13‑Hour Time Shift

This article investigates a puzzling 13‑hour time discrepancy caused by the MySQL 8.x JDBC driver interpreting the ambiguous CST timezone as America/Chicago, explains the underlying driver code and JDK timezone mappings, and provides multiple practical solutions including server settings, driver parameters, and official fixes.

Programmer DD
Programmer DD
Programmer DD
Why MySQL JDBC Driver Misinterprets CST and How to Fix the 13‑Hour Time Shift

1. Demo

pom.xml dependencies (MySQL 8.0.22 driver and Spring Boot):

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.5.4</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.2.0</version>
        <exclusions>
            <exclusion>
                <artifactId>slf4j-api</artifactId>
                <groupId>org.slf4j</groupId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.22</version>
        <scope>runtime</scope>
    </dependency>
</dependencies>

Main application class:

@SpringBootApplication
@MapperScan("com.feng.mysql.rep")
public class MySQLDateMain {
    public static void main(String[] args) {
        SpringApplication.run(MySQLDateMain.class, args);
    }
}

REST controller and MyBatis mapper:

@RestController
public class UserController {
    @Autowired
    private UserRepository userRepository;

    @RequestMapping(value = "/Users/User", method = RequestMethod.POST)
    public String addUser() {
        UserEntity userEntity = new UserEntity();
        userEntity.setAge(12);
        userEntity.setName("tom");
        userEntity.setCreateDate(new Date(System.currentTimeMillis()));
        userEntity.setUpdateDate(new Timestamp(System.currentTimeMillis()));
        userRepository.insertUser(userEntity);
        return "ok";
    }
}

@Mapper
public interface UserRepository {
    @Insert("insert into User (name, age, createDate, updateDate) values (#{name}, #{age}, #{createDate}, #{updateDate})")
    int insertUser(UserEntity userEntity);
}

Database table definition:

CREATE TABLE `work`.`User` (
  `id` int(10) UNSIGNED ZEROFILL NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `age` int NULL DEFAULT NULL,
  `createDate` timestamp NULL DEFAULT NULL,
  `updateDate` datetime NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=29 CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci ROW_FORMAT=Dynamic;

2. Problem Analysis

The inserted timestamps were 13 hours behind the real time after upgrading to MySQL driver 8.0.22. The root cause is the driver’s handling of the ambiguous CST timezone.

In com.mysql.cj.protocol.a.NativeProtocol the driver reads the server timezone and, if it is SYSTEM, falls back to system_time_zone. Then it calls TimeUtil.getCanonicalTimezone which maps the string to a Java TimeZone. The JDK’s ZoneInfoFile contains an old mapping that treats CST as America/Chicago (UTC‑5/‑6), not the expected China Standard Time (UTC+8).

Key snippets from the JDK timezone data (tzdb.dat) show the mapping:

private static String[][] oldMappings = {
    {"CST", "America/Chicago"},
    {"CTT", "Asia/Shanghai"},
    ...
};

Consequently, when the driver formats a java.sql.Timestamp using SimpleDateFormat with the CST identifier, it applies the US Central time, causing a 13‑hour loss (UTC+8 – UTC‑5 = 13).

2.1 Timezone Retrieval in the Driver

The driver initializes the session timezone in com.mysql.cj.jdbc.ConnectionImpl via session.getProtocol().initServerSession(). The method configureTimezone() obtains the server’s time_zone variable, falls back to system_time_zone, then resolves it with TimeUtil.getCanonicalTimezone. If the result is not GMT, it sets the session timezone with TimeZone.getTimeZone(canonicalTimezone).

2.2 How the Mis‑mapping Happens

The JDK’s ZoneInfoFile.getZoneInfo loads the mapping from tzdb.dat. Because CST is mapped to America/Chicago, the driver ends up using the US Central timezone for any CST value.

3. Solutions

Multiple ways to resolve the issue:

Set the MySQL server’s global time_zone to a non‑CST value, e.g. SET GLOBAL time_zone = '+08:00'; Configure the OS timezone (e.g., timedatectl set-timezone Asia/Shanghai on Ubuntu).

Add the JDBC URL parameter serverTimezone=Asia/Shanghai to force the driver to use the correct zone.

Upgrade to MySQL Connector/J 8.0.23 or later, where the driver defaults to the client’s timezone when connectionTimeZone is not set.

Modify the driver code to obtain the timezone from the client instead of the server (not recommended for most users).

3.1 Detailed Fixes

Set MySQL server timezone: SET GLOBAL time_zone = '+08:00'; Or in mysqld.cnf: default-time-zone = '+08:00' Set OS timezone (Ubuntu example): timedatectl set-timezone Asia/Shanghai Add JDBC parameter:

jdbc:mysql://localhost:3306/work?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai

Official driver fix (Connector/J 8.0.23+):

public void configureTimeZone() {
    String connectionTimeZone = getPropertySet().getStringProperty(PropertyKey.connectionTimeZone).getValue();
    TimeZone selectedTz = null;
    if (connectionTimeZone == null || connectionTimeZone.isEmpty() || "LOCAL".equals(connectionTimeZone)) {
        selectedTz = TimeZone.getDefault();
    } else if ("SERVER".equals(connectionTimeZone)) {
        return; // use server timezone later
    } else {
        selectedTz = TimeZone.getTimeZone(ZoneId.of(connectionTimeZone));
    }
    this.serverSession.setSessionTimeZone(selectedTz);
    // optional: send SET SESSION time_zone query
}

Timestamp binding logic (Connector/J 8.0.23):

public void bindTimestamp(int parameterIndex, Timestamp x, Calendar targetCalendar, int fractionalLength, MysqlType targetMysqlType) {
    if (fractionalLength < 0) fractionalLength = 6;
    x = TimeUtil.adjustNanosPrecision(x, fractionalLength, !this.session.getServerSession().isServerTruncatesFracSecs());
    StringBuffer buf = new StringBuffer();
    if (targetCalendar != null) {
        buf.append(TimeUtil.getSimpleDateFormat("''yyyy-MM-dd HH:mm:ss", targetCalendar).format(x));
    } else {
        this.tsdf = TimeUtil.getSimpleDateFormat(this.tsdf, "''yyyy-MM-dd HH:mm:ss",
            targetMysqlType == MysqlType.TIMESTAMP && this.preserveInstants.getValue()
                ? this.session.getServerSession().getSessionTimeZone()
                : this.session.getServerSession().getDefaultTimeZone());
        buf.append(this.tsdf.format(x));
    }
    if (this.session.getServerSession().getCapabilities().serverSupportsFracSecs() && x.getNanos() > 0) {
        buf.append('.');
        buf.append(TimeUtil.formatNanos(x.getNanos(), 6));
    }
    buf.append('\'');
    setValue(parameterIndex, buf.toString(), targetMysqlType);
}

After applying any of the above fixes, the inserted timestamps match the actual time without the 13‑hour offset.

3.2 Official Release Note (Connector/J 8.0.23)

MySQL Connector/J 8.0.23 changes the default connectionTimeZone handling: if not specified, it now uses the client’s default timezone ( TimeZone.getDefault()) instead of the server’s ambiguous CST mapping.

Connector/J 8.0.23 release notes
Connector/J 8.0.23 release notes

Summary

The MySQL JDBC driver mistakenly treats the ambiguous CST timezone as US Central time, causing a 13‑hour discrepancy for applications running in China. The issue stems from JDK’s old timezone mappings. Updating the driver to 8.0.23+, configuring serverTimezone, or adjusting server/OS timezones resolves the problem.

JavaMySQLJDBCTimezoneMySQL8CST
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

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.