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

  1. Configure a concrete class that implements TimestampExtractor<T> with all methods implemented
  2. If you have an abstract base, create a concrete subclass and reference that in WindowConfig
  3. Ensure the class has a public no-arg constructor
  4. 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

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


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/ce0ad7420c5622e8. Report an issue: GitHub.