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

  1. 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
  2. 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
  3. 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)
  4. 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

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


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