flowable/flowable-engine · error · ActivitiException
Error while firing timer: border event activity ${nestedActi
Error message
Error while firing timer: border event activity ${nestedActivityId} not found What it means
Fired by TimerExecuteNestedActivityJobHandler when a persisted timer job fires but the boundary/timer event activity it references no longer exists in the deployed process definition. The job's handler configuration stores a nestedActivityId that is looked up via execution.getProcessDefinition().findActivity(); a null return means the job is stale relative to the current definition. The engine throws ActivitiException to abort job execution rather than silently firing into a non-existent activity.
Source
Thrown at modules/flowable5-engine/src/main/java/org/activiti/engine/impl/jobexecutor/TimerExecuteNestedActivityJobHandler.java:53
public static final String TYPE = "timer-transition";
public static final String PROPERTYNAME_TIMER_ACTIVITY_ID = "activityId";
public static final String PROPERTYNAME_END_DATE_EXPRESSION = "timerEndDate";
@Override
public String getType() {
return TYPE;
}
@Override
public void execute(Job job, String configuration, ExecutionEntity execution, CommandContext commandContext) {
String nestedActivityId = TimerEventHandler.getActivityIdFromConfiguration(configuration);
ActivityImpl borderEventActivity = execution.getProcessDefinition().findActivity(nestedActivityId);
if (borderEventActivity == null) {
throw new ActivitiException("Error while firing timer: border event activity " + nestedActivityId + " not found");
}
try {
if (commandContext.getEventDispatcher().isEnabled()) {
commandContext.getEventDispatcher().dispatchEvent(
ActivitiEventBuilder.createEntityEvent(FlowableEngineEventType.TIMER_FIRED, job),
EngineConfigurationConstants.KEY_PROCESS_ENGINE_CONFIG);
dispatchActivityTimeoutIfNeeded(job, execution, commandContext);
}
borderEventActivity
.getActivityBehavior()
.execute(execution);
} catch (RuntimeException e) {
LOGGER.error("exception during timer execution", e);
throw e;
} catch (Exception e) {View on GitHub (pinned to d6d39ce1c6)
Solutions
- Delete the stale timer job (managementService.deleteJob(jobId)) or let the failed-job retry mechanism fail it (set exception message/stack trace on the job) so it stops blocking the async executor
- Reconcile the deployment: redeploy the original BPMN version that contains the referenced activity, or migrate/restart the process instance so it references a definition containing the activityId
- Check the job's JOB_HANDLER_CFG_ column for the nestedActivityId and verify it exists in the deployed BPMN XML (find the activity by id)
- If whole version sets are stale, clean up orphaned jobs for dead process instances via SQL on ACT_RU_JOB where PROCESS_INSTANCE_ID no longer exists
Example fix
// before: failed job retrying forever // inspect the job and remove it Job job = managementService.createJobQuery().jobId(jobId).singleResult(); managementService.deleteJob(job.getId()); // after: ensure old-version jobs are removed when undeploying repositoryService.deleteDeployment(deploymentId, true); // cascades instances and their jobs
Defensive patterns
Strategy: validation
Validate before calling
Job job = managementService.createJobQuery().jobId(jobId).singleResult();
String cfg = job.getJobHandlerConfiguration();
String activityId = TimerEventHandler.getActivityIdFromConfiguration(cfg); // or parse JSON manually
boolean exists = repositoryService.createProcessDefinitionQuery()
.processDefinitionId(job.getProcessDefinitionId()).singleResult() != null;
if (!exists) { managementService.deleteJob(jobId); return; } Type guard
boolean isStaleTimerJob(Job job) {
return job == null || job.getProcessDefinitionId() == null
|| repositoryService.createProcessDefinitionQuery()
.processDefinitionId(job.getProcessDefinitionId()).singleResult() == null;
} Try / catch
try {
managementService.executeJob(jobId);
} catch (FlowableException e) {
if (e.getMessage() != null && e.getMessage().contains("border event activity")) {
managementService.deleteJob(jobId); // stale job, discard
} else { throw e; }
} Prevention
- Always delete deployments with cascade=true when their instances are no longer needed, so timer jobs are removed too
- Never rename/remove boundary timer activity ids without migrating or terminating running instances that have pending timers
- Audit ACT_RU_JOB rows periodically for jobs whose PROCESS_DEFINITION_ID_ no longer exists in ACT_RE_PROCDEF
- Run the same Flowable version on all cluster nodes to avoid job-configuration mismatches
When it happens
Trigger: A boundary timer job (or intermediate timer catch event job) is persisted, then a new process definition version is deployed and old instances are migrated/removed, so the activityId in the job configuration no longer resolves in the process definition the execution points to.
Common situations: Redeploying an updated BPMN where the boundary event id was renamed or removed while timer jobs from the old version still exist; cascading deletion of process instances without deleting their timer jobs; manually manipulating ACT_RU_JOB rows; restoring a database backup out of sync with deployments.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
- Error reading json value " + configuration + " for job " + j
- Could not find process definition needed for timer start eve
- exception during timer execution for " + job
- Error while firing timer: intermediate event activity ${nest
- exception during timer execution: ${message}
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/d16c50f8f6ca85a6.
Report an issue: GitHub.