flowable/flowable-engine · error · FlowableIllegalArgumentException

Delegate expression did not resolve to an implementation of

Error message

Delegate expression ${aggregation.getImplementation()} did not resolve to an implementation of ${PlanItemVariableAggregator.class}

What it means

resolveVariableAggregator looks up a delegate bean via the aggregation's delegate expression and requires it to implement PlanItemVariableAggregator. If the expression resolves to an object of the wrong type (or non-null but incompatible), Flowable throws this FlowableIllegalArgumentException because the CMMN engine cannot aggregate plan item variables with the given delegate.

Solutions

  1. Make the bean referenced by the delegate expression implement org.flowable.cmmn.engine.impl.variable.PlanItemVariableAggregator
  2. Verify the delegate expression points at the correct bean (print/log aggregation.getImplementation() and check the Spring context bean type)
  3. If no custom aggregation is needed, remove the delegate expression so the engine default variableAggregator from CmmnEngineConfiguration is used
  4. Rebuild/redeploy after fixing so the case definition picks up the corrected class

Example fix

// before
public class MyAggregator { public Object aggregate(...) {...} }
// after
public class MyAggregator implements PlanItemVariableAggregator {
    public Object aggregate(PlanItemInstance planItemInstance, String variableName) {...}
}
Defensive patterns

Strategy: validation

Validate before calling

Object delegate = beanResolver.resolve(aggregation.getImplementation());
if (!(delegate instanceof PlanItemVariableAggregator)) {
    throw new IllegalStateException(aggregation.getImplementation() + " must implement PlanItemVariableAggregator");
}

Type guard

boolean isAggregator(Object o) { return o instanceof PlanItemVariableAggregator; }

Try / catch

try {
    aggregator = CmmnAggregation.resolveVariableAggregator(...);
} catch (FlowableIllegalArgumentException e) {
    logger.error("Aggregation delegate misconfigured: " + e.getMessage());
    throw e;
}

Prevention

When it happens

Trigger: A plan item's childRepetitionRule/variable aggregation declares a delegate expression (e.g. ${aggregation.getImplementation()}) that resolves to a bean not implementing PlanItemVariableAggregator; typo'd bean returning wrong type; custom aggregator class not implementing the interface.

Common situations: Copying a delegate class used elsewhere (e.g. a ValueProvider) into an aggregation role; upgrading Flowable where the aggregator interface changed; Spring bean wiring mistakes where the expression resolves to the factory or wrapper instead of the aggregator.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/variable/CmmnAggregation.java:258

    public static Map<String, List<VariableInstance>> groupVariableInstancesByName(List<? extends VariableInstance> instances) {
        return instances.stream().collect(Collectors.groupingBy(VariableInstance::getName));
    }

    public static PlanItemVariableAggregator resolveVariableAggregator(VariableAggregationDefinition aggregation, DelegatePlanItemInstance planItemInstance) {
        CmmnEngineConfiguration cmmnEngineConfiguration = CommandContextUtil.getCmmnEngineConfiguration();
        if (ImplementationType.IMPLEMENTATION_TYPE_CLASS.equalsIgnoreCase(aggregation.getImplementationType())) {
            return cmmnEngineConfiguration.getClassDelegateFactory().create(aggregation.getImplementation(), null);
        } else if (ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION.equalsIgnoreCase(aggregation.getImplementationType())) {
            Object delegate = DelegateExpressionUtil.resolveDelegateExpression(
                    cmmnEngineConfiguration.getExpressionManager().createExpression(aggregation.getImplementation()),
                    planItemInstance, null);

            if (delegate instanceof PlanItemVariableAggregator) {
                return (PlanItemVariableAggregator) delegate;
            }

            throw new FlowableIllegalArgumentException("Delegate expression " + aggregation.getImplementation() + " did not resolve to an implementation of " + PlanItemVariableAggregator.class);
        } else {
            return cmmnEngineConfiguration.getVariableAggregator();
        }
    }

    public static void sortVariablesByCounter(List<VariableInstance> variableInstances, List<VariableInstance> counterVariableInstances) {
        if (counterVariableInstances == null || counterVariableInstances.isEmpty()) {
            return;
        }
        Map<String, Integer> sortOrder = new HashMap<>();
        for (VariableInstance counterVariable : counterVariableInstances) {
            Object value = counterVariable.getValue();
            String[] values = value.toString().split(COUNTER_VAR_VALUE_SEPARATOR);
            String variableInstanceId = values[0];
            int order = Integer.parseInt(values[1]);
            sortOrder.put(variableInstanceId, order);
        }

View on GitHub (pinned to d6d39ce1c6)