flowable/flowable-engine · error · ActivitiException
UserTask should not be signalled before complete
Error message
UserTask should not be signalled before complete
What it means
Thrown in UserTaskActivityBehavior.signal() when a signal is delivered to an execution that still has active tasks. A user task must be completed (via TaskService.completeTask), which removes the task and leaves the activity; signalling the activity directly before completion is an invalid state transition and always a bug in calling code.
Solutions
- Complete the user task via TaskService.complete(taskId) instead of triggering/signalling the execution
- Before triggering, check the current activity type and route user tasks through the task-completion path
- Query the pending task (TaskService.createTaskQuery().executionId(executionId)) and complete it programmatically
- Remove erroneous signal/trigger calls on user-task executions in custom process-interaction code
Example fix
// before runtimeService.trigger(executionId); // after Task task = taskService.createTaskQuery().executionId(executionId).singleResult(); taskService.complete(task.getId());
Defensive patterns
Strategy: validation
Validate before calling
Task pending = taskService.createTaskQuery().executionId(executionId).singleResult();
if (pending != null) {
taskService.complete(pending.getId());
} else {
runtimeService.trigger(executionId);
} Try / catch
try {
runtimeService.trigger(executionId);
} catch (ActivitiException e) {
if ("UserTask should not be signalled before complete".equals(e.getMessage())) {
// fall back to completing the pending task
}
} Prevention
- Always complete user tasks via TaskService.complete, never via execution signal
- Check current activity type before triggering an execution
- Centralize execution triggering in one utility that handles user tasks specially
When it happens
Trigger: Calling execution.signal(...) / RuntimeService.trigger(executionId) on the execution of an active user task whose tasks list is non-empty, instead of completing the task first.
Common situations: Custom code that drives the process with signal/trigger instead of TaskService; generic activity-completion wrappers that trigger executions without checking the current activity type; migration of code written for receive tasks to user tasks.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- UserTask should not be signalled before complete for +…
- a suspended
- already taking a transition
- Can not activate job
- Can only enable a plan item instance which is in state…
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/a5e31f21bc591260.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable5-engine/src/main/java/org/activiti/engine/impl/bpmn/behavior/UserTaskActivityBehavior.java:234
task.fireEvent(TaskListener.EVENTNAME_CREATE);
// All properties set, now firing 'create' events
if (Context.getProcessEngineConfiguration().getEventDispatcher().isEnabled()) {
Context.getProcessEngineConfiguration().getEventDispatcher().dispatchEvent(
ActivitiEventBuilder.createEntityEvent(FlowableEngineEventType.TASK_CREATED, task),
EngineConfigurationConstants.KEY_PROCESS_ENGINE_CONFIG);
}
}
if (skipUserTask) {
task.complete(null, false, false);
}
}
@Override
public void signal(ActivityExecution execution, String signalName, Object signalData) throws Exception {
if (!((ExecutionEntity) execution).getTasks().isEmpty())
throw new ActivitiException("UserTask should not be signalled before complete");
leave(execution);
}
@SuppressWarnings({"unchecked", "rawtypes"})
protected void handleAssignments(Expression assigneeExpression, Expression ownerExpression, Set<Expression> candidateUserExpressions,
Set<Expression> candidateGroupExpressions, TaskEntity task, ActivityExecution execution) {
if (assigneeExpression != null) {
Object assigneeExpressionValue = assigneeExpression.getValue(execution);
String assigneeValue = null;
if (assigneeExpressionValue != null) {
assigneeValue = assigneeExpressionValue.toString();
}
task.setAssignee(assigneeValue, true, false);
}
if (ownerExpression != null) {
Object ownerExpressionValue = ownerExpression.getValue(execution);View on GitHub (pinned to d6d39ce1c6)