{"record":{"id":"ae35c4e205e6c69e","repo":"flowable/flowable-engine","slug":"formkey-expression-does-not-resolve-to-a-string","errorCode":null,"errorMessage":"FormKey expression does not resolve to a string: ","messagePattern":"FormKey expression does not resolve to a string: ","errorType":"exception","errorClass":"FlowableIllegalArgumentException","httpStatus":null,"severity":"error","filePath":"modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/HumanTaskActivityBehavior.java","lineNumber":310,"sourceCode":"\n    protected void handleFormKey(PlanItemInstanceEntity planItemInstanceEntity, ExpressionManager expressionManager,\n            TaskEntity taskEntity, CreateHumanTaskBeforeContext beforeContext, MigrationContext migrationContext) {\n\n        String formKeyStringValue = null;\n        if (migrationContext != null && migrationContext.getFormKey() != null) {\n            formKeyStringValue = migrationContext.getFormKey();\n            \n        } else if (StringUtils.isNotEmpty(beforeContext.getFormKey())) {\n            formKeyStringValue = beforeContext.getFormKey();\n        }\n        \n        if (StringUtils.isNotEmpty(formKeyStringValue)) {\n            Object formKey = expressionManager.createExpression(formKeyStringValue).getValue(planItemInstanceEntity);\n            if (formKey != null) {\n                if (formKey instanceof String) {\n                    taskEntity.setFormKey((String) formKey);\n                } else {\n                    throw new FlowableIllegalArgumentException(\"FormKey expression does not resolve to a string: \" + beforeContext.getFormKey());\n                }\n            }\n        }\n    }\n\n    protected void handleDueDate(CommandContext commandContext, PlanItemInstanceEntity planItemInstanceEntity,\n            ExpressionManager expressionManager, TaskEntity taskEntity, CreateHumanTaskBeforeContext beforeContext, MigrationContext migrationContext) {\n        \n        String dueDateStringValue = null;\n        if (migrationContext != null && migrationContext.getDueDate() != null) {\n            dueDateStringValue = migrationContext.getDueDate();\n            \n        } else if (StringUtils.isNotEmpty(beforeContext.getDueDate())) {\n            dueDateStringValue = beforeContext.getDueDate();\n        }\n        \n        if (StringUtils.isNotEmpty(dueDateStringValue)) {\n            Object dueDate = expressionManager.createExpression(dueDateStringValue).getValue(planItemInstanceEntity);","sourceCodeStart":292,"sourceCodeEnd":328,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/HumanTaskActivityBehavior.java#L292-L328","documentation":"The form key attribute is an expression that resolved to a non-null object which is not a String. Flowable requires the form key to be a String identifier, so it refuses to set it and throws FlowableIllegalArgumentException with the original expression text.","triggerScenarios":"HumanTaskActivityBehavior.execute -> handleFormKey, when cmmn:formKey contains an expression whose value is a non-String, non-null object.","commonSituations":"Form key expression referencing a numeric form id (Long/Integer); a variable holding a JSON object or Map instead of the form name; bean method returning a FormInfo-like object rather than its key.","solutions":["Make the expression resolve to a String, e.g. ${form.id.toString()} or reference the form name directly.","Set the variable as a String when it is created.","If the form key is static, use a literal value without ${...}.","Catch FlowableIllegalArgumentException around plan item execution and log beforeContext.getFormKey()."],"exampleFix":"// before\n<cmmn:formKey>${formId}</cmmn:formKey>\n// after\n<cmmn:formKey>${formId.toString()}</cmmn:formKey>","handlingStrategy":"type-guard","validationCode":"Object fk = expressionManager.createExpression(formKeyExpr).getValue(scope);\nif (fk != null && !(fk instanceof String)) throw new IllegalArgumentException(\"formKey must resolve to String, got \" + fk.getClass());","typeGuard":"boolean isValidFormKey(Object v) { return v == null || v instanceof String; }","tryCatchPattern":"try {\n    // start human task plan item\n} catch (FlowableIllegalArgumentException e) {\n    if (e.getMessage().startsWith(\"FormKey expression\")) {\n        log.error(\"Form key expression '{}' must yield a String\", e.getMessage());\n    }\n}","preventionTips":["Keep form keys as String identifiers end to end","Use ${id.toString()} when the source value is numeric","Prefer literal form keys over expressions when the form is fixed"],"tags":["cmmn","expression","form-key","type-mismatch"],"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"}