Driver Management¶
How Driver Discovery Works¶
When you call connect(engine="postgres", ...), jdbc4py resolves the JDBC JAR in this order:
- Explicit
jar=orjars=argument — highest priority, used as-is search_paths=[...]directories, thenJDBC4PY_DRIVER_PATHSentries — each validated (must be an absolute path that exists;.jarfiles or directories; glob metacharacters rejected)- The repository's
drivers/<engine>/directory — scanned for JARs matching the engine's glob (e.g.postgresql-*.jar) - 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:
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.