Troubleshooting¶
JVMError: could not find Java¶
jdbc4py looks for Java via JAVA_HOME first, then PATH. If neither works:
# Check if Java is on PATH
java -version
# Set JAVA_HOME if needed
export JAVA_HOME=$(/usr/libexec/java_home) # macOS
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk # Linux
If Java is installed but JAVA_HOME isn't set, jdbc4py will try to find it via PATH. If that fails, set JAVA_HOME explicitly and make sure it points to a Java 11+ runtime.
JVMError: Java version X is below the minimum¶
jdbc4py requires Java 11 or newer. Check what's running:
If you have multiple Java versions, set JAVA_HOME to point to the right one.
DriverNotFoundError¶
jdbc4py couldn't find a JAR for your engine. Check:
- Is the JAR in
drivers/<engine>/? - Does the JAR filename match the expected glob? (e.g.
postgresql-42.7.10.jarmatchespostgresql-*.jar) - If using
JDBC4PY_DRIVER_PATHS, is the path an absolute path that exists?
Enable debug logging to see where jdbc4py is looking:
JVMError: classpath is immutable — cannot add JARs to a running JVM¶
You called connect() with a JAR that isn't already on the JVM classpath, but the JVM was already started by a previous connect() call.
Fix: start the JVM explicitly with all your JARs before the first connection:
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",
])
Connection refused / OperationalError¶
The database isn't reachable. Check:
- Is the database running?
- Is the host and port correct?
- Is there a firewall blocking the connection?
- For Docker, is the container healthy and the port mapped correctly?
DatabaseError: Authentication failed¶
Wrong username or password. Double-check credentials. For Kerberos or IAM auth, check that the JDBC connection properties are set correctly via properties=.
TypeError during parameter binding¶
jdbc4py doesn't know how to bind the value you passed. Common cases:
numpyinteger/float types — cast to Pythonintorfloatfirst- Custom objects — convert to a supported type before passing as a parameter
# Instead of:
cur.execute("INSERT INTO t VALUES (?)", [np.int64(42)])
# Do:
cur.execute("INSERT INTO t VALUES (?)", [int(np.int64(42))])
fetchall() returns empty results or wrong types¶
Check cursor.description after execute() — it shows the column names and types as reported by the JDBC driver. If the types look wrong, it's usually a driver-specific type name that jdbc4py's converter doesn't know about. Open an issue with the driver name and type name.
Arrow / read_sql returns wrong types¶
The Arrow type inference for NUMBER columns without precision (common in Oracle) may default to float64. If you need integers, cast in SQL:
CLOB columns are truncated¶
The Java CLOB cap defaults to 64M characters. If your CLOBs are larger, increase the cap:
import jpype
jpype.startJVM("-Djdbc4py.clob.max_chars=268435456") # 256M chars
import jdbc4py
conn = jdbc4py.connect(...)
This must be set before the JVM starts (before the first connect() call).
Memory usage growing over time¶
If memory usage keeps growing during long-running read_sql calls:
- Use
iter_arrow_batches()with a boundedbatch_sizeinstead ofread_sql()— this lets Python garbage-collect each batch after processing - If using a connection pool, check that connections are being returned (
conn.close()orwith pool.connect() as conn:)
Pool checkout timeout¶
OperationalError: ConnectionPool timed out after Xs waiting for a free connection means all max_connections connections are checked out and none was returned within checkout_timeout seconds.
Options:
- Increase max_connections if the database can handle more connections
- Increase checkout_timeout if bursts are expected
- Check for connection leaks — connections not being returned to the pool
Queries not getting cancelled¶
cursor.cancel() calls Statement.cancel() on the JDBC driver. Whether the driver actually interrupts the query depends on driver support. Most production drivers (PostgreSQL, SQL Server) implement it properly. Some drivers (older versions of ClickHouse JDBC) may not.
If cancel has no effect, check the driver documentation and version.
Thread-related crashes or strange errors¶
Each Python thread that makes JDBC calls must be attached to the JVM. Recent JPype (1.5+) handles this automatically. If you're seeing RuntimeError: thread is not attached to JVM or similar, upgrade JPype or attach manually:
import jpype
from jdbc4py.jvm import JVMManager
JVMManager._attach_thread(jpype)
try:
# ... JDBC calls ...
finally:
JVMManager._detach_thread(jpype)
Enabling debug logging¶
import logging
logging.basicConfig(level=logging.DEBUG, format="%(name)s %(levelname)s: %(message)s")
The loggers you care about are:
jdbc4py.connection— connection open/closejdbc4py.cursor— query execution, timeoutsjdbc4py.pool— checkout/return, evictionjdbc4py.drivers— JAR discovery,JDBC4PY_DRIVER_PATHSvalidationjdbc4py.jvm— JVM startup, thread attachment