apache/beam · error · IllegalArgumentException
CDAP plugin class must have Plugin annotation!
Error message
CDAP plugin class must have Plugin annotation!
What it means
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.
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
Example fix
// before
public class MyStreamSource { ... }
// after
@Plugin(type = PluginConstants.PluginType.STREAMING_SOURCE)
public class MyStreamSource { ... } Defensive patterns
Strategy: validation
Validate before calling
boolean hasCdapPluginAnnotation(Class<?> c) {
return java.util.Arrays.stream(c.getAnnotations())
.anyMatch(a -> a.annotationType().equals(io.cdap.cdap.api.annotation.Plugin.class));
} Type guard
static boolean hasCdapPluginAnnotation(Class<?> c) {
return c.isAnnotationPresent(io.cdap.cdap.api.annotation.Plugin.class);
} Try / catch
try {
expand(...);
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("Plugin annotation")) { /* fix the plugin class annotation */ }
throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Cannot determine context class
- Class does not have a @DefaultCoder annotation.
- Class has a @DefaultCoder annotation with a null value.
- Config class must be not null!
- Provided class should be source or sink plugin
AI-assisted analysis of apache/beam@12126d8942 (2026-09-13).
Data as JSON: /api/errors/9705658c7a4a3bae.
Report an issue: GitHub.
Appendix: source
Thrown at sdks/java/io/cdap/src/main/java/org/apache/beam/sdk/io/cdap/Plugin.java:245
} else if (method.getName().equals(GET_STREAM_METHOD_NAME)) {
return new StreamingSourceContextImpl();
}
}
throw new IllegalStateException("Cannot determine context class");
}
/** Gets value of a plugin type. */
public Boolean isUnbounded() {
Boolean isUnbounded = null;
for (Annotation annotation : getPluginClass().getDeclaredAnnotations()) {
if (annotation.annotationType().equals(io.cdap.cdap.api.annotation.Plugin.class)) {
String pluginType = ((io.cdap.cdap.api.annotation.Plugin) annotation).type();
isUnbounded = pluginType != null && pluginType.startsWith("streaming");
}
}
if (isUnbounded == null) {
throw new IllegalArgumentException("CDAP plugin class must have Plugin annotation!");
}
return isUnbounded;
}
/** Gets a {@link ReceiverBuilder}. */
public ReceiverBuilder<V, ? extends Receiver<V>> getReceiverBuilder() {
checkState(isUnbounded(), "Receiver Builder is supported only for unbounded plugins");
Class<?> pluginClass = getPluginClass();
Class<? extends Receiver<V>> receiverClass = getReceiverClass();
SerializableFunction<PluginConfig, Object[]> getReceiverArgsFromConfigFn =
getGetReceiverArgsFromConfigFn();
PluginConfig pluginConfig = getPluginConfig();
checkStateNotNull(pluginConfig, "Plugin config can not be null!");
checkStateNotNull(pluginClass, "Plugin class can not be null!");
checkStateNotNull(receiverClass, "Receiver class can not be null!");
checkStateNotNull(View on GitHub (pinned to 12126d8942)