{"record":{"id":"414340bfc8dd3f1c","repo":"pentaho/pentaho-kettle","slug":"scriptmeta-exception-unabletoloadadditionalclass","errorCode":null,"errorMessage":"ScriptMeta.Exception.UnableToLoadAdditionalClass","messagePattern":"ScriptMeta\\.Exception\\.UnableToLoadAdditionalClass","errorType":"exception","errorClass":"KettleException","httpStatus":null,"severity":"error","filePath":"engine/src/main/java/org/pentaho/di/trans/steps/script/ScriptMeta.java","lineNumber":842,"sourceCode":"      }\n    } catch ( Exception e ) {\n      throw new KettleException( BaseMessages.getString(\n        PKG, \"ScriptMeta.Exception.UnableToParseXMLforAdditionalClasses\" ), e );\n    }\n  }\n\n  private static Class<?> LoadAdditionalClass( String strJar, String strClassName ) throws KettleException {\n    try {\n      Thread t = Thread.currentThread();\n      ClassLoader cl = t.getContextClassLoader();\n      URL u = new URL( \"jar:file:\" + strJar + \"!/\" );\n      // We never know what else the script wants to load with the class loader, so lets not close it just like that.\n      @SuppressWarnings( \"resource\" )\n      KettleURLClassLoader kl = new KettleURLClassLoader( new URL[] { u }, cl );\n      Class<?> toRun = kl.loadClass( strClassName );\n      return toRun;\n    } catch ( Exception e ) {\n      throw new KettleException(\n        BaseMessages.getString( PKG, \"ScriptMeta.Exception.UnableToLoadAdditionalClass\" ), e );\n    }\n  }\n\n  public ScriptAddClasses[] getAddClasses() {\n    return additionalClasses;\n  }\n\n  public boolean supportsErrorHandling() {\n    return true;\n  }\n\n  /**\n   * @return the replace\n   */\n  public boolean[] getReplace() {\n    return replace;\n  }","sourceCodeStart":824,"sourceCodeEnd":860,"githubUrl":"https://github.com/pentaho/pentaho-kettle/blob/f3058517a153da500bf4551f46d79b91bf8ec552/engine/src/main/java/org/pentaho/di/trans/steps/script/ScriptMeta.java#L824-L860","documentation":"LoadAdditionalClass() creates a KettleURLClassLoader over a jar URL and calls kl.loadClass(strClassName). If the jar can't be opened as a URL, is unreadable, or the class isn't found, the exception is wrapped in KettleException 'UnableToLoadAdditionalClass'.","triggerScenarios":"Loading a Script step's additional class when strJar points to a nonexistent/unreadable file or strClassName is not present in the jar.","commonSituations":"Plugin jar not deployed to plugins/steps/ScriptValues_mod/; jar corrupted; package name in class string wrong (e.g. missing fully-qualified name); running on a different OS with case-sensitive paths.","solutions":["Confirm the jar path used for the URL exists and is readable by the JVM user.","Use the fully qualified class name (com.example.MyHelper, not MyHelper).","Verify the class exists in the jar: jar tf MyHelper.jar | grep MyHelper.","Check the nested 'Caused by' — ClassNotFoundException vs MalformedURLException points to different fixes."],"exampleFix":"// before\nLoadAdditionalClass(path, \"MyHelper\");\n// after\nLoadAdditionalClass(path, \"com.example.helper.MyHelper\");","handlingStrategy":"validation","validationCode":"// Verify class presence in jar before loading\njar tf MyHelper.jar | grep com/example/helper/MyHelper.class","typeGuard":null,"tryCatchPattern":"try { Class<?> c = LoadAdditionalClass(jarPath, className); }\ncatch (KettleException e) {\n  if (e.getCause() instanceof ClassNotFoundException) { /* fix className or jar */ }\n  if (e.getCause() instanceof java.net.MalformedURLException) { /* fix jar path */ }\n}","preventionTips":["Always use fully qualified class names","Verify jars aren't corrupted (jar tf succeeds)","Watch case-sensitive paths on Linux","Keep jar deployment identical across environments"],"tags":["classloading","pdi","jar","plugin"],"backgroundTag":"class-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"}