flowable/flowable-engine · error · FlowableIllegalArgumentException

Can only use a collection of String elements for referencing

Error message

Can only use a collection of String elements for referencing channel model key

What it means

A send-event plan item may define its channel keys via an expression resolving to a collection. getChannelModels() accepts only a collection whose elements are all Strings (each possibly a comma-separated list); any non-String element causes FlowableIllegalArgumentException.

Source

Thrown at modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/SendEventActivityBehavior.java:119

        List<String> channelKeys = new ArrayList<>();

        Map<String, List<ExtensionElement>> extensionElements = planItemInstanceEntity.getPlanItem().getPlanItemDefinition().getExtensionElements();
        if (extensionElements != null) {
            List<ExtensionElement> channelKeyElements = extensionElements.get("channelKey");
            if (channelKeyElements != null && !channelKeyElements.isEmpty()) {
                String channelKey = channelKeyElements.get(0).getElementText();
                if (StringUtils.isNotEmpty(channelKey)) {
                    ExpressionManager expressionManager = CommandContextUtil.getCmmnEngineConfiguration(commandContext).getExpressionManager();
                    Expression expression = expressionManager.createExpression(channelKey);
                    Object resolvedChannelKey = expression.getValue(planItemInstanceEntity);
                    if (resolvedChannelKey instanceof Collection) {
                        for (Object next : (Collection) resolvedChannelKey) {
                            if (next instanceof String) {
                                String[] keys = ((String) next).split(",");
                                channelKeys.addAll(Arrays.asList(keys));

                            } else {
                                throw new FlowableIllegalArgumentException("Can only use a collection of String elements for referencing channel model key");

                            }
                        }

                    } else if (resolvedChannelKey instanceof String) {
                        String[] keys = ((String) resolvedChannelKey).split(",");
                        channelKeys.addAll(Arrays.asList(keys));

                    }
                }
            }
        }

        if (channelKeys.isEmpty()) {
            if (!sendOnSystemChannel) {
                // If the event is going to be send on the system channel then it is allowed to not define any other channels
                throw new FlowableException("No channel keys configured for " + planItemInstanceEntity);
            } else {

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Ensure the expression resolves to Collection<String> — convert in the producing code (e.g. stream().map(String::valueOf).collect(...))
  2. Change the variable to a comma-separated String instead of a collection, which the parser also accepts and splits on commas
  3. Wrap the expression to convert, e.g. ${channels.toString()} only if format matches, or use a dedicated delegate that maps to strings
  4. Validate variable types at the point they are set on the case instance

Example fix

// before
List<Integer> channels = Arrays.asList(1, 2);
caseService.setVariable(caseInstanceId, "channelKeys", channels); // non-String elements
// after
List<String> channels = Arrays.asList("emailChannel", "smsChannel");
caseService.setVariable(caseInstanceId, "channelKeys", channels);
Defensive patterns

Strategy: validation

Validate before calling

Object v = caseService.getVariable(caseInstanceId, "channelKeys");
if (v instanceof Collection) {
    for (Object o : (Collection<?>) v) {
        if (!(o instanceof String)) {
            throw new IllegalArgumentException("channelKeys must be a Collection<String>");
        }
    }
}

Type guard

function isStringCollection(v) { return v instanceof Collection && ((Collection) v).stream().allMatch(o -> o instanceof String); }

Try / catch

try {
    // trigger send-event plan item
} catch (FlowableIllegalArgumentException e) {
    if (e.getMessage().contains("collection of String elements")) {
        // fix variable type at the producer side
    }
}

Prevention

When it happens

Trigger: The channel key expression (channel key/extension element) evaluates to a Collection containing non-String objects (e.g. Integers, domain objects) while execute processes channel models.

Common situations: Expression like ${myChannelKeys} returning List<Integer> or a list of enums; variables set from other services holding non-string types; channel keys stored in a case variable with the wrong type.

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/d8ead7d21d9dc7c6. Report an issue: GitHub.