pentaho/pentaho-kettle · error · KettleDatabaseException

JdbcDriverResolver: driver JAR '" + driverId + ".jar" + "'…

Error message

JdbcDriverResolver: driver JAR '" + driverId + ".jar" + "' not found at any known location Set environment variable or system property 'JDBC_DRIVER_SERVICE_URL' to enable automatic download from the connection-management service.

What it means

JdbcDriverResolver.downloadFromService() is the last step of the resolution chain. When the driver JAR was not found locally or in the drivers directory, it tries to download it from a connection-management service. If neither the JDBC_DRIVER_SERVICE_URL environment variable nor the equivalent system property is set, it cannot download and throws this KettleDatabaseException.

Solutions

  1. Set JDBC_DRIVER_SERVICE_URL (env var or -D system property) to the base URL of the connection-management service
  2. Or manually place <driverId>.jar into the directory named by JDBC_DRIVERS_DIRECTORY
  3. Or bundle the JAR in lib/ so step 1 of resolve() finds it at the configured path
  4. Enable dynamic driver caching so a one-time download persists for later runs

Example fix

// before
$ kettle.sh   # JDBC_DRIVER_SERVICE_URL unset → download impossible
// after
export JDBC_DRIVER_SERVICE_URL=https://driver-service.internal.example.com
kettle.sh
Defensive patterns

Strategy: fallback

Validate before calling

String url = System.getenv( "JDBC_DRIVER_SERVICE_URL" );
if ( url == null || url.trim().isEmpty() ) url = System.getProperty( "JDBC_DRIVER_SERVICE_URL" );
boolean downloadAvailable = url != null && !url.trim().isEmpty();

Try / catch

try { path = JdbcDriverResolver.resolve( driverId ); } catch ( KettleDatabaseException e ) { if ( e.getMessage().contains( "JDBC_DRIVER_SERVICE_URL" ) ) { /* fall back to bundled driver or abort with setup instructions */ } throw e; }

Prevention

When it happens

Trigger: resolve(driverId) found the JAR at no configured path, no drivers directory, and no cached copy, and Const.getJdbcDriverServiceUrl() returned null/blank because JDBC_DRIVER_SERVICE_URL is unset in the environment.

Common situations: Fresh installation where the JDBC driver was never bundled; container image lacking both the driver JAR and the service URL; kettle.properties/env not provisioned in CI; typo in the environment variable name.

Understand the failure class

Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of pentaho/pentaho-kettle@f3058517a1 (2026-09-13). Data as JSON: /api/errors/05d605670c76a07e. Report an issue: GitHub.

Appendix: source

Thrown at core/src/main/java/org/pentaho/di/core/database/JdbcDriverResolver.java:244

   * <p>The download base URL is read from:
   * <ol>
   *   <li>Environment variable {@code JDBC_DRIVER_SERVICE_URL}</li>
   *   <li>System property {@code JDBC_DRIVER_SERVICE_URL}</li>
   * </ol>
   * The JAR is fetched from {@code <baseUrl>/api/v1/connection-drivers/<driverId>/download}.
   *
   * @param driverId       id of the JDBC driver to download from connection-management service
   * @return absolute path of the downloaded JAR
   * @throws KettleDatabaseException if the download URL is not configured or the download fails
   */
  private static String downloadFromService( String driverId )
    throws KettleDatabaseException {

    // Const.getJdbcDriverServiceUrl() applies NVL(getenv, getProperty) — works on all environments.
    String serviceBaseUrl = Const.getJdbcDriverServiceUrl();

        if ( serviceBaseUrl == null || serviceBaseUrl.trim().isEmpty() ) {
            throw new KettleDatabaseException(
                    "JdbcDriverResolver: driver JAR '" + driverId + ".jar" + "' not found at any known location "
                            + "Set environment variable or system property 'JDBC_DRIVER_SERVICE_URL'"
                            + " to enable automatic download from the connection-management service." );
        }

        // One lock per JAR name — threads for *different* JARs never block each other.
        ReentrantLock lock = DOWNLOAD_LOCKS.computeIfAbsent( driverId + ".jar", k -> new ReentrantLock() );
        lock.lock();
        try {
            Path saveDir = resolveWritableSaveDir();
            Path savePath = saveDir.resolve( driverId + ".jar" );

            // Re-check after acquiring the lock: a waiting thread may find the file was already
            // downloaded by the thread that held the lock before it.
            if ( Files.isRegularFile( savePath ) ) {
                log.logBasic( "JdbcDriverResolver: '" + driverId + ".jar" + "' already downloaded by concurrent thread → "
                        + savePath );
                return savePath.toAbsolutePath().toString();

View on GitHub (pinned to f3058517a1)