{"record":{"id":"d8ead7d21d9dc7c6","repo":"flowable/flowable-engine","slug":"can-only-use-a-collection-of-string-elements-for-r","errorCode":null,"errorMessage":"Can only use a collection of String elements for referencing channel model key","messagePattern":"Can only use a collection of String elements for referencing channel model key","errorType":"validation","errorClass":"FlowableIllegalArgumentException","httpStatus":null,"severity":"error","filePath":"modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/SendEventActivityBehavior.java","lineNumber":119,"sourceCode":"        List<String> channelKeys = new ArrayList<>();\n\n        Map<String, List<ExtensionElement>> extensionElements = planItemInstanceEntity.getPlanItem().getPlanItemDefinition().getExtensionElements();\n        if (extensionElements != null) {\n            List<ExtensionElement> channelKeyElements = extensionElements.get(\"channelKey\");\n            if (channelKeyElements != null && !channelKeyElements.isEmpty()) {\n                String channelKey = channelKeyElements.get(0).getElementText();\n                if (StringUtils.isNotEmpty(channelKey)) {\n                    ExpressionManager expressionManager = CommandContextUtil.getCmmnEngineConfiguration(commandContext).getExpressionManager();\n                    Expression expression = expressionManager.createExpression(channelKey);\n                    Object resolvedChannelKey = expression.getValue(planItemInstanceEntity);\n                    if (resolvedChannelKey instanceof Collection) {\n                        for (Object next : (Collection) resolvedChannelKey) {\n                            if (next instanceof String) {\n                                String[] keys = ((String) next).split(\",\");\n                                channelKeys.addAll(Arrays.asList(keys));\n\n                            } else {\n                                throw new FlowableIllegalArgumentException(\"Can only use a collection of String elements for referencing channel model key\");\n\n                            }\n                        }\n\n                    } else if (resolvedChannelKey instanceof String) {\n                        String[] keys = ((String) resolvedChannelKey).split(\",\");\n                        channelKeys.addAll(Arrays.asList(keys));\n\n                    }\n                }\n            }\n        }\n\n        if (channelKeys.isEmpty()) {\n            if (!sendOnSystemChannel) {\n                // If the event is going to be send on the system channel then it is allowed to not define any other channels\n                throw new FlowableException(\"No channel keys configured for \" + planItemInstanceEntity);\n            } else {","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/SendEventActivityBehavior.java#L101-L137","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure the expression resolves to Collection<String> — convert in the producing code (e.g. stream().map(String::valueOf).collect(...))","Change the variable to a comma-separated String instead of a collection, which the parser also accepts and splits on commas","Wrap the expression to convert, e.g. ${channels.toString()} only if format matches, or use a dedicated delegate that maps to strings","Validate variable types at the point they are set on the case instance"],"exampleFix":"// before\nList<Integer> channels = Arrays.asList(1, 2);\ncaseService.setVariable(caseInstanceId, \"channelKeys\", channels); // non-String elements\n// after\nList<String> channels = Arrays.asList(\"emailChannel\", \"smsChannel\");\ncaseService.setVariable(caseInstanceId, \"channelKeys\", channels);","handlingStrategy":"validation","validationCode":"Object v = caseService.getVariable(caseInstanceId, \"channelKeys\");\nif (v instanceof Collection) {\n    for (Object o : (Collection<?>) v) {\n        if (!(o instanceof String)) {\n            throw new IllegalArgumentException(\"channelKeys must be a Collection<String>\");\n        }\n    }\n}","typeGuard":"function isStringCollection(v) { return v instanceof Collection && ((Collection) v).stream().allMatch(o -> o instanceof String); }","tryCatchPattern":"try {\n    // trigger send-event plan item\n} catch (FlowableIllegalArgumentException e) {\n    if (e.getMessage().contains(\"collection of String elements\")) {\n        // fix variable type at the producer side\n    }\n}","preventionTips":["Only set String collections (or comma-separated Strings) for channel key variables","Convert numeric/enum keys to String before setting case variables","Type-check channel variables where they are produced","Document the expected channelKeys variable type in the case model"],"tags":["cmmn","event-registry","argument-type","expression"],"backgroundTag":"type-mismatch","analyzedSha":"d6d39ce1c69ff244f2d9dc6af756a9b95e865586","analyzedAt":"2026-09-11T06:41:19.413Z","contentChangedAt":"2026-09-11T06:41:19.413Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}