{"record":{"id":"2b23704f087ddc67","repo":"pentaho/pentaho-kettle","slug":"no-id-specified-for-plugin-with-class-classname","errorCode":null,"errorMessage":"No ID specified for plugin with class: ${className}","messagePattern":"No ID specified for plugin with class: (.+?)","errorType":"exception","errorClass":"KettlePluginException","httpStatus":null,"severity":"critical","filePath":"core/src/main/java/org/pentaho/di/core/plugins/BasePluginType.java","lineNumber":890,"sourceCode":"   * @param clazz\n   *          The class to use\n   * @param annotation\n   *          The annotation to get information from\n   * @param libraries\n   *          The libraries to add\n   * @param nativePluginType\n   *          Is this a native plugin?\n   * @param pluginFolder\n   *          The plugin folder to use\n   * @throws KettlePluginException\n   */\n  @Override\n  public void handlePluginAnnotation( Class<?> clazz, java.lang.annotation.Annotation annotation,\n    List<String> libraries, boolean nativePluginType, URL pluginFolder ) throws KettlePluginException {\n\n    String idList = extractID( annotation );\n    if ( Utils.isEmpty( idList ) ) {\n      throw new KettlePluginException( \"No ID specified for plugin with class: \" + clazz.getName() );\n    }\n\n    // Only one ID for now\n    String[] ids = idList.split( \",\" );\n\n    String packageName = extractI18nPackageName( annotation );\n    String altPackageName = clazz.getPackage().getName();\n    String pluginName = getTranslation( extractName( annotation ), packageName, altPackageName, clazz );\n    String description = getTranslation( extractDesc( annotation ), packageName, altPackageName, clazz );\n    \n    String category = extractCategory( annotation );\n    \n    //deprecation is either based on a deprecated annotation or a category that contains \"deprecated\" in the translation key\n    boolean deprecated = extractDeprecated( annotation ) ||\n            ( !Utils.isEmpty( category ) ? category.toLowerCase().contains( \"deprecated\" ) : false );\n    category = getTranslation( category, packageName, altPackageName, clazz );\n\n    String imageFile = extractImageFile( annotation );","sourceCodeStart":872,"sourceCodeEnd":908,"githubUrl":"https://github.com/pentaho/pentaho-kettle/blob/f3058517a153da500bf4551f46d79b91bf8ec552/core/src/main/java/org/pentaho/di/core/plugins/BasePluginType.java#L872-L908","documentation":"BasePluginType.handlePluginAnnotation processes a plugin annotation discovered during plugin scanning. Before registering, it calls extractID(annotation); if the annotation yields no ID (empty or null), Kettle cannot key the plugin in the registry, so it throws KettlePluginException naming the annotated class. This indicates the plugin's annotation is missing its required id/idattribute value.","triggerScenarios":"Calling handlePluginAnnotation (directly or via a PluginType's searchForPlugins/annotation scan) on a class whose @Step/@JobEntry/etc. annotation has an empty or null id field, or whose extractID returns empty because the configured idAttribute is not set on the annotation.","commonSituations":"A plugin author forgets to set the id attribute in @Step(id=...) or similar; a custom plugin type defines an idAttribute that doesn't match the annotation's field name; annotation processing picks up a class annotated with an older annotation version lacking the id element.","solutions":["Set the id attribute on the plugin annotation, e.g. change @Step() to @Step(id = \"MyStep\", name = \"MyStep\", ...).","Rebuild the plugin jar so the compiled annotation actually contains the id (stale jars may lack it).","If using a custom plugin type, verify the idAttribute passed to the BasePluginType constructor matches an actual annotation element name.","Check for duplicate/conflicting annotation jars on the plugin classpath and remove stale versions."],"exampleFix":"// before\n@Step(name = \"MyStep\", description = \"Does things\", category = \"Transform\")\npublic class MyStepMeta extends BaseStepMeta { ... }\n\n// after\n@Step(id = \"MyStep\", name = \"MyStep\", description = \"Does things\", category = \"Transform\")\npublic class MyStepMeta extends BaseStepMeta { ... }","handlingStrategy":"validation","validationCode":"// Before scanning/registration, validate each annotated plugin class\nClass<?> clazz = MyStepMeta.class;\nStep ann = clazz.getAnnotation(Step.class);\nif (ann == null || ann.id() == null || ann.id().isEmpty()) {\n  throw new IllegalStateException(\"Plugin annotation missing id: \" + clazz.getName());\n}","typeGuard":"static boolean hasPluginId(Class<?> clazz) {\n  for (java.lang.annotation.Annotation a : clazz.getAnnotations()) {\n    try {\n      Object id = a.getClass().getMethod(\"id\").invoke(a);\n      if (id instanceof String && !((String) id).isEmpty()) return true;\n    } catch (Exception ignored) { }\n  }\n  return false;\n}","tryCatchPattern":"try {\n  pluginType.handlePluginAnnotation(clazz, annotation, libraries, nativePlugin, pluginFolder);\n} catch (KettlePluginException e) {\n  if (e.getMessage().contains(\"No ID specified\")) {\n    log.error(\"Fix annotation id for \" + clazz.getName());\n  }\n}","preventionTips":["Always set the id attribute on plugin annotations (@Step(id=...), @JobEntry(id=...)).","Add a unit test asserting every annotated plugin class hasPluginId().","Keep custom plugin types' idAttribute aligned with the annotation element name.","Rebuild and redeploy jars after annotation changes to avoid stale class files."],"tags":["kettle","plugins","annotation","configuration"],"backgroundTag":"missing-required-config-field","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"}