{"record":{"id":"9705658c7a4a3bae","repo":"apache/beam","slug":"cdap-plugin-class-must-have-plugin-annotation","errorCode":null,"errorMessage":"CDAP plugin class must have Plugin annotation!","messagePattern":"CDAP plugin class must have Plugin annotation!","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"sdks/java/io/cdap/src/main/java/org/apache/beam/sdk/io/cdap/Plugin.java","lineNumber":245,"sourceCode":"      } else if (method.getName().equals(GET_STREAM_METHOD_NAME)) {\n        return new StreamingSourceContextImpl();\n      }\n    }\n    throw new IllegalStateException(\"Cannot determine context class\");\n  }\n\n  /** Gets value of a plugin type. */\n  public Boolean isUnbounded() {\n    Boolean isUnbounded = null;\n\n    for (Annotation annotation : getPluginClass().getDeclaredAnnotations()) {\n      if (annotation.annotationType().equals(io.cdap.cdap.api.annotation.Plugin.class)) {\n        String pluginType = ((io.cdap.cdap.api.annotation.Plugin) annotation).type();\n        isUnbounded = pluginType != null && pluginType.startsWith(\"streaming\");\n      }\n    }\n    if (isUnbounded == null) {\n      throw new IllegalArgumentException(\"CDAP plugin class must have Plugin annotation!\");\n    }\n    return isUnbounded;\n  }\n\n  /** Gets a {@link ReceiverBuilder}. */\n  public ReceiverBuilder<V, ? extends Receiver<V>> getReceiverBuilder() {\n    checkState(isUnbounded(), \"Receiver Builder is supported only for unbounded plugins\");\n\n    Class<?> pluginClass = getPluginClass();\n    Class<? extends Receiver<V>> receiverClass = getReceiverClass();\n    SerializableFunction<PluginConfig, Object[]> getReceiverArgsFromConfigFn =\n        getGetReceiverArgsFromConfigFn();\n    PluginConfig pluginConfig = getPluginConfig();\n\n    checkStateNotNull(pluginConfig, \"Plugin config can not be null!\");\n    checkStateNotNull(pluginClass, \"Plugin class can not be null!\");\n    checkStateNotNull(receiverClass, \"Receiver class can not be null!\");\n    checkStateNotNull(","sourceCodeStart":227,"sourceCodeEnd":263,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/java/io/cdap/src/main/java/org/apache/beam/sdk/io/cdap/Plugin.java#L227-L263","documentation":"Plugin.isUnbounded determines whether a streaming source is unbounded by reading the io.cdap.cdap.api.annotation.Plugin annotation on the plugin class and checking if its type starts with \"streaming\". If no CDAP Plugin annotation is present, isUnbounded remains null and the method throws IllegalArgumentException, since boundedness cannot be determined without the annotation-declared plugin type.","triggerScenarios":"Calling expand/prepareRun/getReceiverBuilder with a CDAP plugin class that lacks @Plugin(type = ...) — e.g., a hand-written class, a test stub, or a plugin whose annotation was lost after build/shading.","commonSituations":"Writing a custom CDAP plugin and forgetting the @Plugin annotation; annotation retention stripped during build; passing a plain config or wrapper class where the actual annotated plugin class was expected; CDAP SDK upgrade changing annotation package so the checked annotation type no longer matches.","solutions":["Annotate the plugin class with io.cdap.cdap.api.annotation.Plugin including a correct type value (e.g., \"streamingsource\", \"batchsource\", \"batchsink\")","Verify the plugin jar retains the annotation at RUNTIME retention after your build/shading","Pass the actual annotated plugin class, not a wrapper or config class","Check imports: annotate with the CDAP Plugin annotation, not another annotation with the same simple name"],"exampleFix":"// before\npublic class MyStreamSource { ... }\n// after\n@Plugin(type = PluginConstants.PluginType.STREAMING_SOURCE)\npublic class MyStreamSource { ... }","handlingStrategy":"validation","validationCode":"boolean hasCdapPluginAnnotation(Class<?> c) {\n  return java.util.Arrays.stream(c.getAnnotations())\n      .anyMatch(a -> a.annotationType().equals(io.cdap.cdap.api.annotation.Plugin.class));\n}","typeGuard":"static boolean hasCdapPluginAnnotation(Class<?> c) {\n  return c.isAnnotationPresent(io.cdap.cdap.api.annotation.Plugin.class);\n}","tryCatchPattern":"try {\n  expand(...);\n} catch (IllegalArgumentException e) {\n  if (e.getMessage().contains(\"Plugin annotation\")) { /* fix the plugin class annotation */ }\n  throw e;\n}","preventionTips":["Always annotate CDAP plugin classes with @Plugin(type = ...) before wrapping them in Beam","Add a startup test asserting c.isAnnotationPresent(io.cdap.cdap.api.annotation.Plugin.class)","Verify annotation RUNTIME retention survives your shading/minification build"],"tags":["java","cdap","beam","annotation","unbounded"],"backgroundTag":"missing-required-annotation","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-20T03:17:13.778Z"}