I have a Spring Boot app which creates a database connection. It's fairly standard code:
SQLServerConnection sqlServerConnection = (SQLServerConnection) DriverManager.getConnection(url);
I checked and the URL is correct and contains the right connection string.
It used to work fine, but after some configuration updates (changed the Gradle version and some other seemingly unrelated things) the code above throws the exception that the driver can't be found.
After some experimenting the fix turned out to be adding this line before trying to establish a connection:
Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
Some background - the app is Spring Boot based. We generate a fat jar and I checked it - it contains the Microsoft driver jar:
$ jar tf app.jar | grep jdbc
BOOT-INF/lib/mssql-jdbc-12.4.1.jre11.jar
My question is - what's happening? From my understanding the Class.forName()
is not required since 1.6.
The reason the Class.forName
is not required, is that JDBC drivers are loaded via the SPI system. This involves asking all entries on the classpath for the resource META-INF/resources/java.sql.Driver
, which is a text file listing one (or more) fully qualified driver class per file. It then loads them all. As if you wrote Class.forName()
for each of them.
Given that your code works fine if you add the forName
but it does not if you don't, your fatjarring has messed up that file. The obvious explanations are:
META-INF/services
need to be merged (simply combine them all into a single file).In general fatjarring is mostly useless. jars can have a classpath. fatjars are big, it takes some time to make them (big files tend to, after all), and thus not using them makes it easier to manage your deps and makes deployments go way, way faster. There is some slight benefit in having a single jar file you can ship around but this is barely a benefit - generally 'here is a jar file, you figure out how to run it' is not appropriate and hasn't been for a decade: The 'modern' (at this point 10 years old) recommended client-side java deployment setup is that you ensure an appropriate JVM is available. Thus, the distro model of 'ship one single jar around' is deprecated as you can't ensure a JVM that way.
TL;DR: Stop fatjarring, it caused this, and many other problems, and adds nothing useful. Alternatively, find a less bad tool / manually fix its mangling of the JDBC driver SPI file / configure it so it no longer does this / remove all but one JDBC driver from the fatjar stack / just roll with the Class.forName
thing... and know that anything else SPI based is probably also broken. I hope you didn't try to add any security or charset providers...