apache/pulsar · error · RuntimeException

User class must have a no-arg constructor

Error message

User class must have a no-arg constructor

What it means

After locating the extractor's no-arg constructor, newInstance() is invoked reflectively. If the constructor is non-public (or the class/constructor is otherwise inaccessible from the executor), IllegalAccessException is rethrown as "User class must have a no-arg constructor". setAccessible(true) mitigates some cases, but inaccessible constructors in restricted contexts still throw. The extractor needs an accessible no-arg constructor.

Source

Thrown at pulsar-functions/instance/src/main/java/org/apache/pulsar/functions/windowing/WindowFunctionExecutor.java:152

            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;
    }

    private TriggerPolicy<Record<T>, ?> getTriggerPolicy(WindowConfig windowConfig, WindowManager<Record<T>> manager,
                                                         EvictionPolicy<Record<T>, ?> evictionPolicy, Context context) {
        if (windowConfig.getSlidingIntervalCount() != null) {

View on GitHub (pinned to 820761864e)

Solutions

  1. Make the no-arg constructor public
  2. Avoid placing the extractor behind JPMS strong encapsulation or add the necessary --add-opens
  3. Verify the class isn't a private nested class; move it to a top-level or public static nested class
  4. Rebuild and redeploy the function

Example fix

// before
public class MyExtractor implements TimestampExtractor<String> {
    private MyExtractor() { }
}
// after
public class MyExtractor implements TimestampExtractor<String> {
    public MyExtractor() { }
}
Defensive patterns

Strategy: validation

Validate before calling

Constructor<?> ctor = Class.forName(className).getDeclaredConstructor();
if (!Modifier.isPublic(ctor.getModifiers())) {
    throw new IllegalStateException("extractor no-arg constructor must be public: " + className);
}
ctor.setAccessible(true); ctor.newInstance(); // verify instantiability

Try / catch

try {
    executor.initialize();
} catch (RuntimeException e) {
    if ("User class must have a no-arg constructor".equals(e.getMessage())) {
        throw new IllegalStateException("make the extractor constructor public and top-level", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Constructor is private/package-private and security manager or module restrictions block setAccessible; getDeclaredConstructor finds the constructor but newInstance cannot invoke it.

Common situations: Extractors written with private singletons; Java module (JPMS) boundaries blocking reflective access; class defined in a package the function instance cannot open reflectively.

Related errors


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