Search code examples
javagradlejdbcjarbuild.gradle

Shadow Jar add two JDBC drivers to one jar file with gradle


I am encountering an issue when I try to build a fat jar file with gradle. Is there a way to configure gradle to support multiple JDBC drivers in the JAR file? I noticed in my META-INF/services/java.sqlDriver that it will take whatever JDBC library comes first. So when running the jar file I would often see this error.

java.sql.SQLException: No suitable driver
    at java.sql/java.sql.DriverManager.getDriver(DriverManager.java:299)

I see that the solution to this issue revolves around adding Class.forName('jdbcDriver') before I initialize my db connections, but I see that for JTDS libraries you do not need that anymore because of JDBC type 4 can automatically detect which driver you need based on the connection string.

My build.gradle file

plugins {
    id("application")
    id("com.github.johnrengelman.shadow") version "7.1.0"
}

dependencies {
    implementation group: 'net.sourceforge.jtds', name: 'jtds', version: '1.3.1'
    implementation group: 'mysql', name: 'mysql-connector-java', version: '8.0.26'
}

My connection string

jdbc:jtds:sqlserver://localhost:1111/db;user=TEST;password=DUMMY

I am using Java 17 and Gradle 7.3.3


Solution

  • You need to configure the Gradle Shadow Plugin to merge the provider-configuration files. How to do that is documented here:

    Merging Service Descriptor Files

    Java libraries often contain service descriptors files in the META-INF/services directory of the JAR. A service descriptor typically contains a line delimited list of classes that are supported for a particular service. At runtime, this file is read and used to configure library or application behavior.

    Multiple dependencies may use the same service descriptor file name. In this case, it is generally desired to merge the content of each instance of the file into a single output file. The ServiceFileTransformer class is used to perform this merging. By default, it will merge each copy of a file under META-INF/services into a single file in the output JAR.

    // Merging Service Files
    shadowJar {
      mergeServiceFiles()
    }
    

    The above code snippet is a convenience syntax for calling transform(ServiceFileTransformer.class)

    If using the Kotlin DSL, it would look like:

    tasks {
        shadowJar {
            mergeServiceFiles()
        }
    }
    

    Or:

    tasks.shadowJar {
        mergeServiceFiles()
    }