apache/pulsar · error · RuntimeException
User class must be concrete
Error message
User class must be concrete
What it means
getTimeStampExtractor instantiates the configured timestamp extractor reflectively via a no-arg constructor. InstantiationException (or its modern wrapper) is caught and rethrown as "User class must be concrete", meaning the extractor class is abstract or an interface and cannot be instantiated. The configured class must be a concrete class with an accessible no-arg constructor.
Source
Thrown at pulsar-functions/instance/src/main/java/org/apache/pulsar/functions/windowing/WindowFunctionExecutor.java:148
private TimestampExtractor<T> getTimeStampExtractor(WindowConfig windowConfig) {
Class<?> theCls;
try {
theCls = Class.forName(windowConfig.getTimestampExtractorClassName(),
true, Thread.currentThread().getContextClassLoader());
} catch (ClassNotFoundException | NoClassDefFoundError cnfe) {
throw new RuntimeException(
String.format("Timestamp extractor class %s must be in class path",
windowConfig.getTimestampExtractorClassName()), cnfe);
}
Object result;
try {
Constructor<?> constructor = theCls.getDeclaredConstructor();
constructor.setAccessible(true);
result = constructor.newInstance();
} catch (InstantiationException ie) {
throw new RuntimeException("User class must be concrete", ie);
} catch (NoSuchMethodException e) {
throw new RuntimeException("User class doesn't have such method", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("User class must have a no-arg constructor", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("User class constructor throws exception", e);
}
Class<?>[] timestampExtractorTypeArgs = TypeResolver.resolveRawArguments(
TimestampExtractor.class, result.getClass());
Class<?>[] typeArgs = TypeResolver.resolveRawArguments(Function.class, this.getClass());
if (!typeArgs[0].equals(timestampExtractorTypeArgs[0])) {
throw new RuntimeException(
"Inconsistent types found between function input type and timestamp extractor type: "
+ " function type = " + typeArgs[0] + ", timestamp extractor type = "
+ timestampExtractorTypeArgs[0]);
}
return (TimestampExtractor<T>) result;
}View on GitHub (pinned to 820761864e)
Solutions
- Configure a concrete class that implements TimestampExtractor<T> with all methods implemented
- If you have an abstract base, create a concrete subclass and reference that in WindowConfig
- Ensure the class has a public no-arg constructor
- Rebuild and redeploy the function jar
Example fix
// before
public abstract class MyExtractor implements TimestampExtractor<String> { }
// after
public class MyExtractor implements TimestampExtractor<String> {
public long extractTimestamp(String record) { ... }
} Defensive patterns
Strategy: validation
Validate before calling
Class<?> c = Class.forName(className);
if (Modifier.isAbstract(c.getModifiers()) || c.isInterface()) {
throw new IllegalStateException("extractor must be a concrete class: " + className);
} Try / catch
try {
executor.initialize();
} catch (RuntimeException e) {
if ("User class must be concrete".equals(e.getMessage())) {
throw new IllegalStateException("configure a concrete TimestampExtractor subclass", e);
}
throw e;
} Prevention
- Never point config at interfaces or abstract adapters
- Add a no-arg constructor to every extractor class
- Unit-test `new MyExtractor()` before deploying
When it happens
Trigger: timestampExtractorClassName points at an abstract base class, interface, or anonymous/abstract generic subclass; reflection calls constructor.newInstance() and gets InstantiationException.
Common situations: Pointing config at the TimestampExtractor interface itself; creating an abstract adapter class and forgetting to implement the extract method; lambda or generic anonymous classes whose target type is abstract.
Related errors
- User class doesn't have such method
- User class must have a no-arg constructor
- User class constructor throws exception
- functions may not take more than two arguments, but function
- function takes two arguments, but the first is not Context.
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/ce0ad7420c5622e8.
Report an issue: GitHub.