Skip to content

Driver Management

How Driver Discovery Works

When you call connect(engine="postgres", ...), jdbc4py resolves the JDBC JAR in this order:

  1. Explicit jar= or jars= argument — highest priority, used as-is
  2. search_paths=[...] directories, then JDBC4PY_DRIVER_PATHS entries — each validated (must be an absolute path that exists; .jar files or directories; glob metacharacters rejected)
  3. The repository's drivers/<engine>/ directory — scanned for JARs matching the engine's glob (e.g. postgresql-*.jar)
  4. The current working directory's drivers/ folder — also scanned, for convenience

Because a JDBC driver JAR is arbitrary code loaded into the in-process JVM, the CWD scan emits a one-time security warning; set JDBC4PY_DISABLE_CWD_DRIVER_SCAN=1 to turn it off. Within a chosen directory the newest matching version wins.

If no JAR is found, jdbc4py raises DriverNotFoundError with a message describing where it looked.

Using a Custom Driver

Pass the JAR path directly:

from jdbc4py import connect

conn = connect(
    engine="postgres",
    host="localhost",
    database="mydb",
    user="me",
    password="secret",
    jar="/opt/drivers/postgresql-42.7.10.jar",
)

For a completely custom JDBC driver with no built-in engine support:

conn = connect(
    jdbc_url="jdbc:myfancydb://host:1234/mydb",
    driver="com.example.FancyDriver",
    jar="/opt/drivers/fancydb-1.0.jar",
    user="me",
    password="secret",
)

The drivers/ Directory

The repository ships with JARs for common databases already in drivers/:

drivers/
  postgres/   postgresql-42.7.10.jar
  mysql/      mysql-connector-j-9.6.0.jar
  mssql/      mssql-jdbc-13.2.1.jre11.jar
  oracle/     ojdbc11-23.26.1.0.0.jar
  clickhouse/ clickhouse-jdbc-0.9.7-all-dependencies.jar
  trino/      trino-jdbc-479.jar
  snowflake/  snowflake-jdbc-4.0.2.jar
  databricks/ databricks-jdbc-3.2.1.jar

To add a driver or update a version, drop the new JAR in the matching directory. If multiple JARs match the glob pattern, jdbc4py picks the one with the highest version number.

JDBC4PY_DRIVER_PATHS

JDBC4PY_DRIVER_PATHS is a colon-separated (Unix) or semicolon-separated (Windows) list of filesystem locations to scan:

export JDBC4PY_DRIVER_PATHS="/opt/jdbc-drivers:/usr/local/jdbc"

Each entry can be a directory (scanned for JARs) or a single .jar file.

Security

Every entry is validated before use. Entries that fail validation are skipped with a warning — they never raise an exception, so a bad entry doesn't break startup, but it should be investigated.

Validation rules:

Rule Why
Must be an absolute path Relative paths depend on working directory, which may be attacker-controlled
Must exist on disk A missing path is likely a misconfiguration
Files must end in .jar Non-JAR files have no place on the classpath
No glob metacharacters (*, ?, [, ]) Metacharacters indicate injection or misconfiguration

If you see warnings like JDBC4PY_DRIVER_PATHS entry '/opt/*.jar' contains glob metacharacters, that entry was rejected. Fix the path to point to a specific directory or JAR file.

Treat JDBC4PY_DRIVER_PATHS as a privileged configuration value. Every path in it is loaded onto the JVM classpath unconditionally. An attacker who can write to this variable can execute arbitrary code inside the JVM by supplying a crafted JAR. In containerized deployments, set it at the infrastructure level, not in application code.

See operator-guide.md for deployment guidance.

Multiple Databases in One Process

When connecting to multiple databases simultaneously, all JDBC JARs must be on the classpath before the first connection is opened. The JVM classpath is frozen at startup — you can't add JARs to a running JVM.

The simplest approach is to start the JVM explicitly with all JARs upfront:

from jdbc4py.jvm import JVMManager

JVMManager.ensure_started(jars=[
    "drivers/postgres/postgresql-42.7.10.jar",
    "drivers/mysql/mysql-connector-j-9.1.0.jar",
    "drivers/oracle/ojdbc11-23.26.1.0.0.jar",
])

# Now connect() calls don't need to specify jars again
pg   = connect(engine="postgres",  host="pg-host",    database="analytics", ...)
mysql = connect(engine="mysql",    host="mysql-host", database="users",     ...)

If you forget and pass a new JAR to connect() after the JVM is running, you'll get a JVMError explaining that the classpath is immutable.

Cloud Databases

Snowflake

from jdbc4py import connect_snowflake

conn = connect_snowflake(
    account="acme.eu-west-1",
    database="ANALYTICS",
    schema="PUBLIC",
    warehouse="COMPUTE_WH",
    user="svc_user",
    password="secret",
)

Drop the Snowflake JDBC JAR in drivers/snowflake/ and it's picked up automatically.

Databricks

from jdbc4py import connect_databricks

conn = connect_databricks(
    server_hostname="dbc-123456.cloud.databricks.com",
    http_path="/sql/1.0/warehouses/abc123",
    token="dapi-...",
)

Drop the Databricks JDBC JAR in drivers/databricks/.

Google Cloud Spanner

from jdbc4py.cloud import connect_spanner

# Application Default Credentials (ADC) — works on GCE/GKE and after
# `gcloud auth application-default login` locally.
conn = connect_spanner(
    project="my-project",
    instance="my-instance",
    database="my-database",
)

# Service account key file
conn = connect_spanner(
    project="my-project",
    instance="my-instance",
    database="my-database",
    credentials_file="/path/to/service-account-key.json",
)

Drop the Cloud Spanner JDBC fat JAR (google-cloud-spanner-jdbc-*-single-jar-with-dependencies.jar) in drivers/spanner/. Use the single-jar-with-dependencies build — it bundles all dependencies and avoids classpath conflicts.

Engine Reference

Engine key Driver class Default port JAR glob
postgres org.postgresql.Driver 5432 postgresql-*.jar
mysql com.mysql.cj.jdbc.Driver 3306 mysql-connector-j-*.jar
mssql com.microsoft.sqlserver.jdbc.SQLServerDriver 1433 mssql-jdbc-*.jar
oracle oracle.jdbc.OracleDriver 1521 ojdbc11-*.jar
clickhouse com.clickhouse.jdbc.ClickHouseDriver 8123 clickhouse-jdbc-*-all-dependencies.jar
trino io.trino.jdbc.TrinoDriver 8080 trino-jdbc-*.jar
presto com.facebook.presto.jdbc.PrestoDriver 8080 presto-jdbc-*.jar
snowflake net.snowflake.client.jdbc.SnowflakeDriver 443 snowflake-jdbc-*.jar
databricks com.databricks.client.jdbc.Driver 443 databricks-jdbc-*.jar
hana com.sap.db.jdbc.Driver 30015 ngdbc-*.jar
sqlite org.sqlite.JDBC sqlite-jdbc*.jar
mariadb org.mariadb.jdbc.Driver 3306 mariadb-java-client-*.jar
spanner com.google.cloud.spanner.jdbc.JdbcDriver google-cloud-spanner-jdbc-*-single-jar-with-dependencies.jar
bigquery com.google.cloud.bigquery.jdbc.BigQueryDriver 443 google-cloud-bigquery-jdbc-*-all.jar
sybase_ase com.sybase.jdbc4.jdbc.SybDriver 5000 jconn4.jar / jtds-*.jar
sybase_iq com.sybase.jdbc4.jdbc.SybDriver 2638 jconn4.jar / jtds-*.jar

hana accepts aliases sap_hana/saphana/hdb; sybase_ase accepts sybase/ase; sybase_iq accepts iq/sap_iq.