flowable/flowable-engine · error · FlowableIllegalArgumentException

<delegateClassName> doesn't implement CustomPropertiesResolv

Error message

<delegateClassName> doesn't implement CustomPropertiesResolver

What it means

ClassDelegate instantiates the class configured as a custom properties resolver and casts it to CustomPropertiesResolver. When the instantiated object does not implement that interface, a FlowableIllegalArgumentException naming the class and the expected interface is thrown. Flowable requires this interface to resolve the custom properties passed to transaction-dependent listeners.

Source

Thrown at modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/helper/ClassDelegate.java:156

    protected ExecutionListener getExecutionListenerInstance() {
        return new DelegateExecutionListener(instantiateDelegate(className, fieldDeclarations));
    }

    protected TransactionDependentExecutionListener getTransactionDependentExecutionListenerInstance() {
        Object delegateInstance = instantiateDelegate(className, fieldDeclarations);
        if (delegateInstance instanceof TransactionDependentExecutionListener) {
            return (TransactionDependentExecutionListener) delegateInstance;
        } else {
            throw new FlowableIllegalArgumentException(delegateInstance.getClass().getName() + " doesn't implement " + TransactionDependentExecutionListener.class);
        }
    }

    protected CustomPropertiesResolver getCustomPropertiesResolverInstance() {
        Object delegateInstance = instantiateDelegate(className, fieldDeclarations);
        if (delegateInstance instanceof CustomPropertiesResolver) {
            return (CustomPropertiesResolver) delegateInstance;
        } else {
            throw new FlowableIllegalArgumentException(delegateInstance.getClass().getName() + " doesn't implement " + CustomPropertiesResolver.class);
        }
    }

    protected TaskListener getTaskListenerInstance() {
        Object delegateInstance = instantiateDelegate(className, fieldDeclarations);
        if (delegateInstance instanceof TaskListener) {
            return (TaskListener) delegateInstance;
        } else {
            throw new FlowableIllegalArgumentException(delegateInstance.getClass().getName() + " doesn't implement " + TaskListener.class);
        }
    }

    protected TransactionDependentTaskListener getTransactionDependentTaskListenerInstance() {
        Object delegateInstance = instantiateDelegate(className, fieldDeclarations);
        if (delegateInstance instanceof TransactionDependentTaskListener) {
            return (TransactionDependentTaskListener) delegateInstance;
        } else {
            throw new FlowableIllegalArgumentException(delegateInstance.getClass().getName() + " doesn't implement " + TransactionDependentTaskListener.class);

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Make the configured class implement org.flowable.engine.delegate.CustomPropertiesResolver (getCustomPropertiesMap method).
  2. Check the customPropertiesResolver class attribute points to the intended fully qualified class name.
  3. Redeploy so the runtime classpath contains the interface implementation.
  4. If no custom properties are needed, remove the customPropertiesResolver attribute entirely.

Example fix

// before
public class MyResolver { public Map<String,Object> getProps() { ... } }
// after
public class MyResolver implements CustomPropertiesResolver {
    @Override
    public Map<String, Object> getCustomPropertiesMap(DelegateExecution execution) { ... }
}
Defensive patterns

Strategy: validation

Validate before calling

Class<?> c = Class.forName(cfg.getCustomPropertiesResolverClass());
if (!CustomPropertiesResolver.class.isAssignableFrom(c))
    throw new IllegalStateException("Class must implement CustomPropertiesResolver");

Type guard

boolean isValidResolver(Object o) { return o instanceof CustomPropertiesResolver; }

Try / catch

try {
    processEngine.getRuntimeService().startProcessInstanceByKey("proc");
} catch (FlowableIllegalArgumentException e) {
    if (e.getMessage().contains("CustomPropertiesResolver")) { /* fix class */ }
    throw e;
}

Prevention

When it happens

Trigger: A BPMN element declares flowable:customPropertiesResolver class='<delegateClassName>' and the class does not implement org.flowable.engine.delegate.CustomPropertiesResolver; getCustomPropertiesMap is called when the transaction-dependent listener fires.

Common situations: Reusing a random bean as customPropertiesResolver; typos in the class attribute pointing at another class; the class implements the listener interface but not the resolver interface; stale deployment artifacts.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11). Data as JSON: /api/errors/c43791bb8076fc68. Report an issue: GitHub.