JDK-8060068/JDK-8067904 DriverManager clean-up introduces bootstrap problem that breaks production drivers

Robert Gibson robbiexgibson at yahoo.com
Fri Apr 3 09:33:12 UTC 2015


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)



More information about the core-libs-dev mailing list