Root Cause Analysis of MySQL 8.0 Driver Upgrade Causing Druid Connection Pool Blocking

After upgrading to MySQL 8.0, the Druid connection pool experiences severe performance degradation with many threads blocked due to repeated failed class loading of the removed MySQLConnection class, and the issue is resolved by upgrading Druid to a newer version.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Root Cause Analysis of MySQL 8.0 Driver Upgrade Causing Druid Connection Pool Blocking

Phenomenon : After upgrading the MySQL driver to 8.0, the Druid connection pool shows that most SQL execution times exceed 200 ms under high concurrency, and many threads become blocked.

Thread dump analysis reveals a BLOCKED state at com.alibaba.druid.util.Utils.loadClass(Utils.java:220):

"http-nio-5366-exec-48" #210 daemon prio=5 os_prio=0 tid=0x00000000023d0800 nid=0x3be9 waiting for monitor entry [0x00007fa4c1400000]
  java.lang.Thread.State: BLOCKED (on object monitor)
    at org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader.loadClass(TomcatEmbeddedWebappClassLoader.java:66)
    - waiting to lock <0x0000000775af0960> (a java.lang.Object)
    at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1186)
    at com.alibaba.druid.util.Utils.loadClass(Utils.java:220)
    at com.alibaba.druid.util.MySqlUtils.getLastPacketReceivedTimeMs(MySqlUtils.java:372)

Root Cause Analysis : The method MySqlUtils.getLastPacketReceivedTimeMs() tries to load the class com.mysql.jdbc.MySQLConnection. In MySQL 8.0 this class was renamed to com.mysql.cj.jdbc.ConnectionImpl, so the load fails. The utility method Utils.loadClass() catches ClassNotFoundException and returns null without propagating the error, causing the failure to be silent.

public class MySqlUtils {
    public static long getLastPacketReceivedTimeMs(Connection conn) throws SQLException {
        if (class_connectionImpl == null && !class_connectionImpl_Error) {
            try {
                class_connectionImpl = Utils.loadClass("com.mysql.jdbc.MySQLConnection");
            } catch (Throwable error) {
                class_connectionImpl_Error = true;
            }
        }
        // ... (omitted for brevity) ...
        if (class_connectionImpl == null) {
            return -1;
        }
        // further reflection logic ...
    }
}

The Utils.loadClass() implementation also swallows ClassNotFoundException:

public static Class<?> loadClass(String className) {
    Class<?> clazz = null;
    if (className == null) {
        return null;
    }
    try {
        return Class.forName(className);
    } catch (ClassNotFoundException e) {
        // skip
    }
    ClassLoader ctxClassLoader = Thread.currentThread().getContextClassLoader();
    if (ctxClassLoader != null) {
        try {
            clazz = ctxClassLoader.loadClass(className);
        } catch (ClassNotFoundException e) {
            // skip
        }
    }
    return clazz;
}

Because the missing class is never reported, each call to getLastPacketReceivedTimeMs() repeatedly attempts to load com.mysql.jdbc.MySQLConnection. The loading is performed by TomcatEmbeddedWebappClassLoader, whose loadClass() method is synchronized:

public class TomcatEmbeddedWebappClassLoader extends ParallelWebappClassLoader {
    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (JreCompat.isGraalAvailable() ? this : getClassLoadingLock(name)) {
            Class<?> result = findExistingLoadedClass(name);
            result = (result != null) ? result : doLoadClass(name);
            if (result == null) {
                throw new ClassNotFoundException(name);
            }
            return resolveIfNecessary(result, resolve);
        }
    }
}

This synchronization creates a lock contention point; under high concurrency the repeated failed loads cause many threads to block, leading to the observed >200 ms latency.

When is the method invoked? The getLastPacketReceivedTimeMs() method is called from DruidAbstractDataSource.testConnectionInternal(), which is part of Druid's connection‑validation logic (parameters testOnBorrow, testOnReturn, testWhileIdle). With testOnBorrow=true, every connection acquisition triggers the costly class‑loading path, dramatically degrading performance.

Solution : The issue exists in Druid 1.x versions ≤ 1.1.22. Upgrading to Druid ≥ 1.1.23 or to any 1.2.x version eliminates the bug.

Reference: GitHub issue #3808 .

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.

BackendjavaConnection PoolDruid
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

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.