{"record":{"id":"9f37d8f6477c8a74","repo":"pentaho/pentaho-kettle","slug":"dynamic-driver-path-does-not-point-to-a-jar-file","errorCode":null,"errorMessage":"Dynamic driver path does not point to a JAR file: {resolvedPath}","messagePattern":"Dynamic driver path does not point to a JAR file: (.+?)","errorType":"exception","errorClass":"KettleDatabaseException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/org/pentaho/di/core/database/Database.java","lineNumber":896,"sourceCode":"   * <p><b>Cached path</b> ({@code KETTLE_DYNAMIC_DRIVER_CACHE_ENABLED=Y}):\n   * Delegates to {@link DynamicDriverCache#getOrLoadDriver}, which reuses the same\n   * {@link Driver} instance and classloader JVM-wide. No JAR reads after the first connect.\n   * {@link #dynamicDriverClassLoader} is left {@code null} because the classloader lifetime\n   * is managed by {@link DynamicDriverCache}, not by this {@link Database} instance.\n   *\n   * <p><b>No-cache path</b> (default — property absent or not {@code Y}):\n   * Creates a fresh {@link ChildFirstURLClassLoader} per connection, loads the driver class,\n   * and stores the loader in {@link #dynamicDriverClassLoader} so that\n   * {@link #closeDynamicClassLoader()} closes it (and releases the JAR file handle) on\n   * {@link #disconnect()}. Each connection is fully isolated with no shared JVM state.\n   */\n  private void loadDynamicDriver( String driverId, String effectiveClassName, List<String> listDriverExtraJars ) throws KettleDatabaseException {\n    // Always resolve first — returns immediately if driverId exists on disk,\n    // otherwise walks the fallback chain. The resolved path is stable for the connection lifetime.\n    String resolvedPath = JdbcDriverResolver.resolve( driverId );\n\n    if ( !resolvedPath.toLowerCase().endsWith( \".jar\" ) ) {\n      throw new KettleDatabaseException( \"Dynamic driver path does not point to a JAR file: \" + resolvedPath );\n    }\n\n    List<String> extraJarPaths = JdbcDriverResolver.resolveAll( listDriverExtraJars );\n    boolean cacheEnabled = \"Y\".equalsIgnoreCase(\n      EnvUtil.getSystemProperty( Const.KETTLE_DYNAMIC_DRIVER_CACHE_ENABLED ) );\n\n    if ( cacheEnabled ) {\n      // Cached path: Driver is shared JVM-wide; classloader is owned by DynamicDriverCache.\n      dynamicDriver.set( DynamicDriverCache.getInstance().getOrLoadDriver( resolvedPath, effectiveClassName, extraJarPaths ) );\n      dynamicDriverClassLoader.set( null );\n    } else {\n      // No-cache path: fresh classloader and Driver per connection; closed on disconnect().\n      List<URL> urls = JdbcDriverResolver.buildUrlList( resolvedPath, extraJarPaths );\n      ChildFirstURLClassLoader loader = null;\n      try {\n        loader = new ChildFirstURLClassLoader( urls.toArray( new URL[ 0 ] ), Database.class.getClassLoader() );\n        Class<?> driverClass = loader.loadClass( effectiveClassName );\n        Driver driver = (Driver) driverClass.getDeclaredConstructor().newInstance();","sourceCodeStart":878,"sourceCodeEnd":914,"githubUrl":"https://github.com/pentaho/pentaho-kettle/blob/f3058517a153da500bf4551f46d79b91bf8ec552/core/src/main/java/org/pentaho/di/core/database/Database.java#L878-L914","documentation":"loadDynamicDriver resolves a driverId to a file path via JdbcDriverResolver.resolve and then requires that path to end with '.jar'. If resolution returns a directory, a metadata URL, or a non-jar artifact, loading cannot proceed and this error is thrown. It guards the URLClassLoader which needs an actual JAR.","triggerScenarios":"connectUsingClass -> loadDynamicDriver for a driverId whose JdbcDriverResolver.resolve returns a path not ending in .jar — e.g. resolver falls back to a folder, the configured driver path points to a directory, or the artifact is a .zip/.so.","commonSituations":"Misconfigured dynamic-driver directory pointing at an extracted (unpacked) driver instead of the JAR; resolver fallback chain returns a base path; user placed a zip download of a driver without renaming; driver repository layout changed.","solutions":["Check the resolvedPath in the message and ensure it points to the actual driver .jar file","Fix the dynamic driver configuration/path so the resolver finds the JAR (not its containing directory)","Verify JdbcDriverResolver's fallback chain and the driverId spelling","If the driver ships as .zip, extract and point to the contained JAR"],"exampleFix":"// before\nString path = \"/opt/drivers/mysql-connector-j-8.4.0\"; // directory\n// after\nString path = \"/opt/drivers/mysql-connector-j-8.4.0/mysql-connector-j-8.4.0.jar\";","handlingStrategy":"validation","validationCode":"File f = new File(JdbcDriverResolver.resolve(driverId));\nif (!f.isFile() || !f.getName().toLowerCase().endsWith(\".jar\")) throw new IllegalStateException(\"Not a JAR: \" + f);","typeGuard":"boolean isJarPath(String p) { return p != null && p.toLowerCase().endsWith(\".jar\") && new File(p).isFile(); }","tryCatchPattern":"try { db.connect(); } catch (KettleDatabaseException e) { if (e.getMessage().startsWith(\"Dynamic driver path does not point to a JAR file\")) { log.error(\"Fix driver path: \" + e.getMessage()); } else throw e; }","preventionTips":["Point dynamic driver config at actual .jar files, not directories or zips","Pre-validate resolved paths during deployment scripts","Keep driver repositories in a documented, flat layout","Extract zipped drivers and reference the inner JAR"],"tags":["jdbc","classpath","file","dynamic-driver"],"backgroundTag":"file-not-found","analyzedSha":"f3058517a153da500bf4551f46d79b91bf8ec552","analyzedAt":"2026-09-13T14:04:16.340Z","contentChangedAt":"2026-09-13T14:04:16.340Z","schemaVersion":2},"datasetVersion":"2026-09-20T23:17:15.980Z"}