JDK8编译代码在JDK21环境运行报NoClassDefFoundError问题求助
问题解决方案:JDK8编译代码在JDK21运行时找不到java.sql.Driver
问题背景
使用JDK8编译的Jar包在JDK21环境运行时,抛出java.lang.NoClassDefFoundError: java/sql/Driver,根因是java.lang.ClassNotFoundException: java.sql.Driver,堆栈信息如下:
java.lang.NoClassDefFoundError: java/sql/Driver at java.base/java.lang.ClassLoader.defineClass1(Native Method) at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1027) at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:150) at com.thunisoft.tas.server.util.ClosableClassLoader.defineClass(ClosableClassLoader.java:105) at com.thunisoft.tas.server.util.ClosableClassLoader.findClass(ClosableClassLoader.java:52) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:593) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526) at com.thunisoft.tas.server.module.jdbc.JdbcService.injectDriver(JdbcService.java:628) at com.thunisoft.tas.server.module.jdbc.JdbcService.doStart(JdbcService.java:83) at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:73) at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:169) at org.eclipse.jetty.server.Server.start(Server.java:423) at org.eclipse.jetty.util.component.ContainerLifeCycle.addBean(ContainerLifeCycle.java:384) at org.eclipse.jetty.util.component.ContainerLifeCycle.addBean(ContainerLifeCycle.java:313) at com.thunisoft.tas.server.TasManager.doStart(TasManager.java:162) at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:73) at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:169) at org.eclipse.jetty.server.Server.start(Server.java:423) at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:117) at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:97) at org.eclipse.jetty.server.Server.doStart(Server.java:387) at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:73) at org.eclipse.jetty.xml.XmlConfiguration.lambda$main$3(XmlConfiguration.java:1931) at java.base/java.security.AccessController.doPrivileged(AccessController.java:571) at org.eclipse.jetty.xml.XmlConfiguration.main(XmlConfiguration.java:1881) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at org.eclipse.jetty.start.Main.invokeMain(Main.java:218) at org.eclipse.jetty.start.Main.start(Main.java:491) at org.eclipse.jetty.start.Main.main(Main.java:77) at com.thunisoft.tas.start.TasServiceWrapperListener.doStart(TasServiceWrapperListener.java:71) at com.thunisoft.tas.start.TasServiceWrapperListener.access$000(TasServiceWrapperListener.java:15) at com.thunisoft.tas.start.TasServiceWrapperListener$1.run(TasServiceWrapperListener.java:46) at java.base/java.lang.Thread.run(Thread.java:1583)
根因堆栈:
Caused by: java.lang.ClassNotFoundException: java.sql.Driver at com.thunisoft.tas.server.util.ClosableClassLoader.findClass(ClosableClassLoader.java:57) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:593) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526) ... 34 common frames omitted
问题原因
Java 9及以后引入了模块系统,java.sql.Driver所在的java.sql模块不再默认被所有类加载器自动访问。自定义的ClosableClassLoader没有正确处理模块化环境下的类加载逻辑,无法从系统模块中加载该类;同时JDK8中该类属于核心类库,JDK21中则属于独立模块,直接跨版本运行会出现类路径/模块路径的适配问题。
解决方案
- 启动时显式添加java.sql模块:在启动Jar包的命令中加入
--add-modules java.sql参数,确保JDK21的模块系统将该模块暴露给应用类加载器。例如:java --add-modules java.sql -jar your-app.jar - 修复自定义类加载器逻辑:修改
ClosableClassLoader的findClass或loadClass方法,对于java.sql.Driver这类系统核心类,委托给父类加载器(如AppClassLoader)去加载,而不是自己尝试查找。示例逻辑:@Override protected Class<?> findClass(String name) throws ClassNotFoundException { if (name.startsWith("java.sql.") || name.equals("java.sql.Driver")) { return getParent().loadClass(name); } // 原有自定义类加载逻辑 } - 更新JDBC驱动版本:使用兼容JDK9+的新版JDBC驱动,比如MySQL Connector/J 8.0+、PostgreSQL Driver 42.2+等。新版驱动支持模块化规范,会通过
ServiceLoader自动注册驱动,无需手动加载java.sql.Driver类,避免依赖问题。 - 移除手动加载Driver的代码:如果代码中存在
Class.forName("com.mysql.cj.jdbc.Driver")这类手动加载驱动的代码,可以直接删除。JDBC 4.0及以后的规范中,驱动会自动通过META-INF/services/java.sql.Driver文件注册,不需要显式调用加载。
内容的提问来源于stack exchange,提问作者yusen zhang
相关产品推荐
相关产品推荐

