flowable/flowable-engine · error · FlowableException

Expression

Error message

Expression 

What it means

After creating the external worker job, the topic expression is evaluated at runtime; the result must be a non-empty string to become the job's handler configuration. If the expression evaluates to null or the empty string, the engine throws this FlowableException naming the expression, the value, and the plan item.

Source

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

                if (categoryValue != null) {
                    job.setCategory(categoryValue.toString());
                }
        }

        job.setJobType(JobEntity.JOB_TYPE_EXTERNAL_WORKER);
        job.setRetries(jobServiceConfiguration.getAsyncExecutorNumberOfRetries());

        // Inherit tenant id (if applicable)
        if (planItemInstanceEntity.getTenantId() != null) {
            job.setTenantId(planItemInstanceEntity.getTenantId());
        }

        Expression expression = CommandContextUtil.getExpressionManager(commandContext).createExpression(jobTopicExpression);
        Object expressionValue = expression.getValue(planItemInstanceEntity);
        if (expressionValue != null && !expressionValue.toString().isEmpty()) {
            job.setJobHandlerConfiguration(expressionValue.toString());
        } else {
            throw new FlowableException("Expression " + jobTopicExpression + " did not evaluate to a valid value (non empty String). Was: " + expressionValue + ". For " + planItemInstanceEntity);
        }

        jobService.insertExternalWorkerJob(job);

        if (interceptor != null) {
            interceptor.afterCreateExternalWorkerJob(new CreateCmmnExternalWorkerJobAfterContext(
                    serviceTask,
                    job,
                    planItemInstanceEntity
            ));
        }
    }

    protected String getJobCategory(BaseElement baseElement) {
        List<ExtensionElement> jobCategoryElements = baseElement.getExtensionElements().get("jobCategory");
        if (jobCategoryElements != null && jobCategoryElements.size() > 0) {
            return jobCategoryElements.get(0).getElementText();
        }

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Initialize the topic variable before the external worker task activates (case start variable, form field, or preceding task).
  2. Correct the expression variable name in the case model to match an existing case variable.
  3. Provide a static topic string if the topic does not need to be dynamic.
  4. Add a validation/sentry so the task only activates when the topic variable is non-empty.

Example fix

// before
caseRuntimeService.setVariable(caseInstanceId, "topc", "loan-processing"); // typo -> expression ${topicName} -> null
// after
caseRuntimeService.setVariable(caseInstanceId, "topicName", "loan-processing");
Defensive patterns

Strategy: validation

Validate before calling

Object topic = caseVariables.get("topicName");
if (!(topic instanceof String s) || s.isEmpty()) {
    throw new IllegalArgumentException("topicName variable must be a non-empty string before external worker task activates");
}

Type guard

if (topicValue instanceof String s && !s.isEmpty()) { /* valid topic */ }

Try / catch

try {
    caseRuntimeService.triggerPlanItemInstance(planItemId);
} catch (FlowableException e) {
    if (e.getMessage().startsWith("Expression ") && e.getMessage().contains("did not evaluate to a valid value")) {
        log.error("Topic expression failed for case {}", caseInstanceId);
    }
    throw e;
}

Prevention

When it happens

Trigger: Expression jobTopicExpression (e.g. ${topicName}) evaluates to null, empty string, or an object whose toString() is empty when the external worker task plan item executes.

Common situations: Case variable holding the topic not set before activation; expression resolves to a null-valued data object field; variable name typo; variable set only for some case instances (per-tenant variation).

Related errors


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