apache/pulsar · error · RuntimeException

User class must be concrete

Error message

User class must be concrete

What it means

createInstance instantiates the class reflectively via a no-arg constructor. InstantiationException is thrown by newInstance() when the class is abstract or an interface, so it is wrapped in this RuntimeException. It means the configured user class cannot be instantiated because it is not concrete.

Source

Thrown at pulsar-common/src/main/java/org/apache/pulsar/common/util/Reflections.java:90

            throw new RuntimeException("User class must be in class path", cnfe);
        }
        if (!xface.isAssignableFrom(theCls)) {
            throw new RuntimeException(userClassName + " does not implement " + xface.getName());
        }
        @SuppressWarnings("unchecked") // safe: theCls is verified to be assignable to xface
        Class<T> tCls = (Class<T>) theCls.asSubclass(xface);
        T result;
        try {
            @SuppressWarnings("unchecked") // safe: constructor cache is keyed by theCls which extends T
            Constructor<T> meth = (Constructor<T>) constructorCache.get(theCls);
            if (null == meth) {
                meth = tCls.getDeclaredConstructor();
                meth.setAccessible(true);
                constructorCache.put(theCls, meth);
            }
            result = meth.newInstance();
        } catch (InstantiationException ie) {
            throw new RuntimeException("User class must be concrete", ie);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("User class must have a no-arg constructor", e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("User class must have a public constructor", e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException("User class constructor throws exception", e);
        }
        return result;

    }

    /**
     * Create an instance of <code>userClassName</code> using provided <code>classLoader</code>.
     *
     * @param userClassName user class name
     * @param classLoader class loader to load the class.
     * @return the instance
     */

View on GitHub (pinned to 820761864e)

Solutions

  1. Configure the fully qualified name of a concrete (non-abstract) class
  2. Make the class concrete by implementing all abstract methods, or add a concrete subclass and use that
  3. Check the plugin jar actually contains the concrete implementation, not only the abstract base

Example fix

// before
public abstract class MyFilter implements Filter { }
// after
public class MyConcreteFilter extends MyFilter { } // use MyConcreteFilter in config
Defensive patterns

Strategy: validation

Validate before calling

// reject abstract classes/interfaces before instantiation
Class<?> cls = Class.forName(className, true, classLoader);
if (Modifier.isAbstract(cls.getModifiers()) || cls.isInterface()) {
    throw new IllegalArgumentException("Configured class must be concrete: " + className);
}

Type guard

boolean isConcrete(Class<?> cls) { return !cls.isInterface() && !Modifier.isAbstract(cls.getModifiers()); }

Try / catch

try {
    T obj = Reflections.createInstance(className, XFace.class, classLoader);
} catch (RuntimeException e) {
    if (e.getMessage().equals("User class must be concrete")) {
        throw new IllegalArgumentException("Configure a concrete subclass of " + className, e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Configuring an abstract class or interface name as the user class, so Constructor.newInstance() raises InstantiationException.

Common situations: Pointing config at a base/abstract adapter class instead of a concrete subclass; refactoring left only abstract implementations in a plugin jar.

Related errors


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