Hi there, We are doing some early testing with JDK 9 and have discovered that the changes made to java.sql.DriverManager back in November/December have introduced an incompatibility with our JDBC driver (that we have used unchanged since Java 6) when it is pre-loaded with Class.forName (as recommended by Tomcat and specified as being harmless in the JavaDoc). We filed a bug report but it got an incident ID JI-9019539 and then disappeared into a black hole.
In the hope of the problem getting a bit more attention I attach a reproducible test case that is representative of the commercially-available JDBC driver that we use (hint: the name rhymes with MyFace and it's used in a lot of banks). I guess the problem is that the driver is calling DriverManager.getDrivers() as part of its registration process, which is now causing the stack overflow given at the end of this mail, after the code. If you compile the attached files into test.jar and run java -cp test.jar JDBCTest n java -cp test.jar JDBCTest y then the test should print Trying to connect to jdbc:mydriver OK in both cases (as it does with Java 8). When running with Java 9, the second command prints Trying to connect to jdbc:mydriver FAIL No suitable driver found for jdbc:mydriver Hope this is helpful. Regards, Robert JDBCTest.java import java.io.*; import java.sql.*; import java.util.*; public class JDBCTest { public static void main(String... argv) throws Exception { if (argv.length > 1) DriverManager.setLogWriter(new PrintWriter(System.out)); String[] urls = new String[] {"jdbc:mydriver"}; if (argv.length > 0 && argv[0].equals("y")) { loadClass("MyDriver"); } for (String url : urls) { System.out.println("Trying to connect to " + url); try { Connection c = DriverManager.getConnection (url); if (c == null) System.out.println("FAIL"); else System.out.println("OK"); }catch(SQLException e) { System.out.println("FAIL"); System.out.println(e.getMessage()); } } } public static void loadClass(String classname) { try { Class.forName(classname); } catch(ClassNotFoundException e) { System.out.println("Class not found: " + classname); } } } MyDriver.java import java.sql.*; import java.util.Enumeration; import java.util.Properties; import java.util.logging.Logger; public class MyDriver implements java.sql.Driver { static { new MyDriver(); } public MyDriver() { this.registerWithDriverManager(); } public final Connection connect(String url, Properties props) throws SQLException { return acceptsURL(url) ? new MyConnection() : null; } public boolean acceptsURL(String url) throws SQLException { return url.startsWith("jdbc:mydriver"); } public DriverPropertyInfo[] getPropertyInfo(String var1, Properties props) throws SQLException { return null; } public int getMajorVersion() { return 0; } public int getMinorVersion() { return 1; } public boolean jdbcCompliant() { return false; } public Logger getParentLogger() throws SQLFeatureNotSupportedException { throw new SQLFeatureNotSupportedException("No logging for you"); } protected void registerWithDriverManager() { try { synchronized(DriverManager.class) { DriverManager.registerDriver(this); Enumeration e = DriverManager.getDrivers(); while(e.hasMoreElements()) { Driver d = (Driver)e.nextElement(); if(d instanceof MyDriver && d != this) { DriverManager.deregisterDriver(d); } } } } catch (SQLException ex) { } } } MyConnection.java import java.sql.*; import java.util.Map; import java.util.Properties; import java.util.concurrent.Executor; public class MyConnection implements Connection { @Override public Statement createStatement() throws SQLException { return null; } @Override public PreparedStatement prepareStatement(String sql) throws SQLException { return null; } @Override public CallableStatement prepareCall(String sql) throws SQLException { return null; } @Override public String nativeSQL(String sql) throws SQLException { return null; } @Override public void setAutoCommit(boolean autoCommit) throws SQLException { } @Override public boolean getAutoCommit() throws SQLException { return false; } @Override public void commit() throws SQLException { } @Override public void rollback() throws SQLException { } @Override public void close() throws SQLException { } @Override public boolean isClosed() throws SQLException { return false; } @Override public DatabaseMetaData getMetaData() throws SQLException { return null; } @Override public void setReadOnly(boolean readOnly) throws SQLException { } @Override public boolean isReadOnly() throws SQLException { return false; } @Override public void setCatalog(String catalog) throws SQLException { } @Override public String getCatalog() throws SQLException { return null; } @Override public void setTransactionIsolation(int level) throws SQLException { } @Override public int getTransactionIsolation() throws SQLException { return 0; } @Override public SQLWarning getWarnings() throws SQLException { return null; } @Override public void clearWarnings() throws SQLException { } @Override public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { return null; } @Override public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { return null; } @Override public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { return null; } @Override public Map<String, Class<?>> getTypeMap() throws SQLException { return null; } @Override public void setTypeMap(Map<String, Class<?>> map) throws SQLException { } @Override public void setHoldability(int holdability) throws SQLException { } @Override public int getHoldability() throws SQLException { return 0; } @Override public Savepoint setSavepoint() throws SQLException { return null; } @Override public Savepoint setSavepoint(String name) throws SQLException { return null; } @Override public void rollback(Savepoint savepoint) throws SQLException { } @Override public void releaseSavepoint(Savepoint savepoint) throws SQLException { } @Override public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { return null; } @Override public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { return null; } @Override public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { return null; } @Override public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { return null; } @Override public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { return null; } @Override public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { return null; } @Override public Clob createClob() throws SQLException { return null; } @Override public Blob createBlob() throws SQLException { return null; } @Override public NClob createNClob() throws SQLException { return null; } @Override public SQLXML createSQLXML() throws SQLException { return null; } @Override public boolean isValid(int timeout) throws SQLException { return false; } @Override public void setClientInfo(String name, String value) throws SQLClientInfoException { } @Override public void setClientInfo(Properties properties) throws SQLClientInfoException { } @Override public String getClientInfo(String name) throws SQLException { return null; } @Override public Properties getClientInfo() throws SQLException { return null; } @Override public Array createArrayOf(String typeName, Object[] elements) throws SQLException { return null; } @Override public Struct createStruct(String typeName, Object[] attributes) throws SQLException { return null; } @Override public void setSchema(String schema) throws SQLException { } @Override public String getSchema() throws SQLException { return null; } @Override public void abort(Executor executor) throws SQLException { } @Override public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException { } @Override public int getNetworkTimeout() throws SQLException { return 0; } @Override public <T> T unwrap(Class<T> iface) throws SQLException { return null; } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return false; } } Stack overflow (recursive call to new MyDriver()) [1] MyDriver.registerWithDriverManager (MyDriver.java:47) [2] MyDriver.<init> (MyDriver.java:12) [3] sun.reflect.NativeConstructorAccessorImpl.newInstance0 (native method) [4] sun.reflect.NativeConstructorAccessorImpl.newInstance (NativeConstructorAccessorImpl.java:62) [5] sun.reflect.DelegatingConstructorAccessorImpl.newInstance (DelegatingConstructorAccessorImpl.java:45) [6] java.lang.reflect.Constructor.newInstance (Constructor.java:425) [7] java.lang.Class.newInstance (Class.java:464) [8] java.util.ServiceLoader$LazyIterator.nextService (ServiceLoader.java:378) [9] java.util.ServiceLoader$LazyIterator.next (ServiceLoader.java:402) [10] java.util.ServiceLoader$1.next (ServiceLoader.java:478) [11] java.sql.DriverManager$2.run (DriverManager.java:614) [12] java.sql.DriverManager$2.run (DriverManager.java:594) [13] java.security.AccessController.doPrivileged (native method) [14] java.sql.DriverManager.ensureDriversInitialized (DriverManager.java:594) [15] java.sql.DriverManager.getDrivers (DriverManager.java:437) [16] MyDriver.registerWithDriverManager (MyDriver.java:47) [17] MyDriver.<init> (MyDriver.java:12) [18] MyDriver.<clinit> (MyDriver.java:8) [19] java.lang.Class.forName0 (native method) [20] java.lang.Class.forName (Class.java:286) [21] JDBCTest.loadClass (JDBCTest.java:28) [22] JDBCTest.main (JDBCTest.java:12)