FAQ¶
Do I need to install Java?¶
Yes. jdbc4py embeds a JVM in your Python process, which requires a Java 11+ runtime. A JRE is enough — you only need a full JDK if you're building the helper JAR from source.
Why is the first connection slow?¶
JVM startup takes 200–500 ms the first time. After that, connections open in 40–60 ms (JDBC negotiation). If you're making many connections, use the connection pool — connections stay warm and checkout is near-instant.
Can I use jdbc4py without the helper JAR?¶
Yes. Without jdbc4py-helper.jar, jdbc4py falls back to row-by-row JDBC fetching. The read_sql(format="arrow") path won't work, but cursor.fetchall() and everything else will. Build the helper JAR with ./java/jdbc4py-helper/build.sh to enable Arrow bulk reads.
Is it thread-safe?¶
The connection pool is thread-safe — share it freely. Individual Connection and Cursor objects are not — use one per thread. See architecture.md for the full concurrency model.
Can I use jdbc4py with async code (asyncio)?¶
Yes, via jdbc4py.aio. JDBC itself is a blocking API, so aio runs the blocking
calls off the event loop on a dedicated worker thread per connection:
import asyncio
from jdbc4py.aio import connect
async def main():
async with await connect(engine="postgres", host="localhost", database="mydb",
user="me", password="secret") as conn:
async with await conn.execute("SELECT id, name FROM users") as cur:
async for row in cur:
print(row)
asyncio.run(main())
For one-off offloading there's also run_query_async() (a single query on a
background thread). Native reactive JDBC (R2DBC) is a different API and is not
supported.
Does jdbc4py work with SQLAlchemy?¶
Yes. Installing jdbc4py registers a +jdbc4py driver for the standard SQLAlchemy
backends, so SQLAlchemy — and therefore pandas and ORMs — can talk to any JDBC
database:
from sqlalchemy import create_engine
import pandas as pd
engine = create_engine("postgresql+jdbc4py://me:secret@localhost:5432/mydb")
df = pd.read_sql("SELECT * FROM events", engine)
URL prefixes: postgresql, mysql, mariadb, mssql, oracle, sqlite
(each with +jdbc4py). Install with pip install "jdbc4py[sqlalchemy]".
Can I connect to multiple databases at once?¶
Yes. Connections are independent — there's no global state beyond the JVM. The limitation is that all driver JARs must be on the classpath before the first connection. See driver-management.md.
How do I use jdbc4py with a connection pool in a web framework?¶
Treat it like any other connection pool. Create a ConnectionPool at application startup, check out a connection per request, return it when done:
# At startup
pool = ConnectionPool(engine="postgres", host="db", ...)
# Per request
with pool.connect() as conn:
result = conn.read_sql("SELECT ...", format="pandas")
Does jdbc4py support SSL / TLS?¶
SSL is configured through JDBC connection properties. The exact property names vary by driver:
# PostgreSQL
conn = connect(
engine="postgres",
host="db.example.com",
database="mydb",
user="me",
password="secret",
properties={"ssl": "true", "sslmode": "verify-full"},
)
# SQL Server
conn = connect(
engine="mssql",
host="db.example.com",
database="mydb",
user="me",
password="secret",
ssl=True,
)
Check your JDBC driver's documentation for the exact property names and values.
What happens to credentials in error messages?¶
jdbc4py redacts credentials from all exception messages before they're raised. Passwords, tokens, API keys, and similar values are replaced with ***. This covers both JDBC URL query parameters and connection property maps. See operator-guide.md for the full list of redacted keys.
How do I run queries in parallel?¶
Give each thread its own connection:
import concurrent.futures
from jdbc4py import ConnectionPool
pool = ConnectionPool(engine="postgres", host="localhost", ...)
def run_query(sql):
with pool.connect() as conn:
return conn.read_sql(sql, format="pandas")
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as ex:
futures = [ex.submit(run_query, sql) for sql in queries]
results = [f.result() for f in futures]
What's the difference between read_sql and cursor.fetchall()?¶
read_sql uses the Arrow IPC path: data serializes on the JVM side and deserializes once in Python. It returns a DataFrame or Arrow table directly.
cursor.fetchall() is standard DBAPI — each row comes back as a tuple, each cell crosses the JNI boundary individually. It's faster for small results; Arrow is faster above ~10K rows.
Can I write data back to the database?¶
Yes. jdbc4py supports writes from Arrow, pandas, Polars, and DuckDB:
conn.write_pandas(df, "my_table")
conn.write_polars(df, "my_table", if_exists="replace")
conn.write_arrow(table, "my_table", batch_size=50_000)
conn.write_duckdb(rel, "my_table")
if_exists controls behavior when the target table already exists: "append" (default), "replace" (drop + recreate), or "fail".
Is Oracle supported?¶
Yes. Drop the ojdbc11.jar (or ojdbc8.jar) in drivers/oracle/. Oracle types including NUMBER, DATE, TIMESTAMP WITH TIME ZONE, INTERVAL YEAR TO MONTH, INTERVAL DAY TO SECOND, BLOB, CLOB, and XMLTYPE are supported.
How do I see jdbc4py's logs?¶
jdbc4py uses Python's standard logging under the jdbc4py namespace and, like
a well-behaved library, stays silent until your app configures logging (it
attaches a NullHandler and never calls basicConfig() itself). To see its
records:
import logging
logging.basicConfig(level=logging.INFO)
logging.getLogger("jdbc4py").setLevel(logging.DEBUG) # connection/pool/JVM detail
Per-subsystem loggers (jdbc4py.connection, jdbc4py.pool, jdbc4py.jvm,
jdbc4py.drivers, …) let you tune verbosity per area. See
operator-guide.md for the production alerting checklist.
Where do I report bugs?¶
Open an issue on GitHub. Include the jdbc4py version, Python version, Java version, database engine and version, and the full traceback.