21. Supported Workflow Patterns

1. Sequential Approval

Tasks flow from one user to another in sequence.

  • Example: Employee submits → Manager reviews → Result notification

2. Review/Revision Loop

A gateway routes the process back to a previous task based on a decision.

  • Example: If ${reviewDecision == 'revise'}, loop back to preparation task.

3. Dynamic Role Assignment

Process initiator selects a role; the task is assigned to that group.

  • Example pattern: Use a role_* field to capture a role, then assign with flowable:candidateGroups="${role_dept}".

4. Dynamic User Assignment

Process initiator selects a specific user; the next task is assigned to them.

  • Demonstrated in: candidate_interview.bpmn (uses users_panel to assign interviewers)

5. File Exchange Workflow

Users can upload files during a task, and subsequent users can view/download them.

  • Demonstrated in: expense_reimbursement.bpmn (receipt upload), policy_acknowledgment.bpmn (policy document)

6. Date Handling Patterns

Dates in Workingflow are stored as java.util.Date for compatibility with Flowable's internal DateFormType. This section covers all common date scenarios.

Capabilities Summary

# Scenario Method How
1 Show date read-only in next task Native BPMN type="date" writable="false"
2 Use date as timer escalation deadline Native BPMN <timeDate>${dueDate}</timeDate>
3a Compare two dates in a gateway Native UEL ${endDate.after(startDate)}
3b Compare date to "now" in a gateway GraalJS + Gateway Script sets boolean → gateway branches
4 Capture workflow start time for later use GraalJS Script stores new java.util.Date()
5 Edit a date in a loop or subsequent task Native BPMN type="date" (writable) — pre-populated

Available Methods (java.util.Date)

Method Returns Usage
.after(otherDate) boolean Is this date after the other?
.before(otherDate) boolean Is this date before the other?
.getTime() long Milliseconds since epoch (for arithmetic)
.equals(otherDate) boolean Exact equality check
.toInstant() Instant Convert to java.time.Instant for further operations

Scenario 1: Read-Only Date Display

A date entered in one task is displayed as a disabled field in a later task.

<!-- Task 1: User enters a date -->
<activiti:formProperty id="dueDate" name="Due Date" type="date" required="true"/>

<!-- Task 2: Same date shown read-only -->
<activiti:formProperty id="dueDate" name="Due Date" type="date" writable="false"/>

Scenario 2: Date as Timer Deadline

A user-entered date triggers an escalation timer on the next task.

<!-- User enters a deadline in a previous task -->
<activiti:formProperty id="deadline" name="Deadline" type="date" required="true"/>

<!-- Boundary timer fires at the user-entered date -->
<boundaryEvent id="deadlineTimer" attachedToRef="reviewTask" cancelActivity="false">
    <timerEventDefinition>
        <timeDate>${deadline}</timeDate>
    </timerEventDefinition>
</boundaryEvent>

Scenario 3a: Compare Two Dates in a Gateway (Native)

java.util.Date has .after() and .before() methods, usable directly in UEL expressions.

<exclusiveGateway id="dateCheck"/>

<sequenceFlow sourceRef="dateCheck" targetRef="validPath">
    <conditionExpression xsi:type="tFormalExpression">${endDate.after(startDate)}</conditionExpression>
</sequenceFlow>

<sequenceFlow sourceRef="dateCheck" targetRef="invalidPath">
    <conditionExpression xsi:type="tFormalExpression">${!endDate.after(startDate)}</conditionExpression>
</sequenceFlow>

Scenario 3b: Compare Date to "Now" in a Gateway (GraalJS)

Use a Script Task before the gateway to compute a boolean.

<scriptTask id="checkOverdue" name="Check If Overdue" scriptFormat="super-js">
  <script>
    var dueDate = execution.getVariable('dueDate');
    execution.setVariable('isOverdue', dueDate.before(new java.util.Date()));
  </script>
</scriptTask>

<sequenceFlow sourceRef="checkOverdue" targetRef="overdueGateway"/>

<exclusiveGateway id="overdueGateway"/>

<sequenceFlow sourceRef="overdueGateway" targetRef="escalate">
    <conditionExpression xsi:type="tFormalExpression">${isOverdue}</conditionExpression>
</sequenceFlow>

<sequenceFlow sourceRef="overdueGateway" targetRef="continueNormally">
    <conditionExpression xsi:type="tFormalExpression">${!isOverdue}</conditionExpression>
</sequenceFlow>

Scenario 4: Capture Workflow Start Time

Use a Script Task immediately after the Start Event to record the current timestamp.

<startEvent id="start" flowable:initiator="initiator"/>
<sequenceFlow sourceRef="start" targetRef="captureStart"/>

<scriptTask id="captureStart" name="Capture Start Time" scriptFormat="super-js">
  <script>execution.setVariable('processStartDate', new java.util.Date());</script>
</scriptTask>

The processStartDate variable can then be used in any of the patterns above (display, timer, gateway comparison).

Scenario 5: Editable Date in a Loop

When a gateway loops back to a task, the date field is pre-populated from the process variable. The user can change it.

<!-- Revision task with editable date -->
<userTask id="reviseRequest" name="Revise Request" flowable:assignee="${initiator}">
    <extensionElements>
        <activiti:formProperty id="startDate" name="Start Date" type="date" required="true"/>
        <activiti:formProperty id="endDate" name="End Date" type="date" required="true"/>
    </extensionElements>
</userTask>

<!-- Gateway loops back -->
<sequenceFlow sourceRef="decisionGateway" targetRef="reviseRequest">
    <conditionExpression xsi:type="tFormalExpression">${decision == 'revise'}</conditionExpression>
</sequenceFlow>

Date Arithmetic in Scripts

For advanced date calculations (e.g., "is this more than 3 days old?"), use GraalJS:

<scriptTask id="dateCalc" name="Date Arithmetic" scriptFormat="super-js">
  <script>
    var startDate = execution.getVariable('startDate');
    var now = new java.util.Date();

    // Days between two dates
    var diffMs = now.getTime() - startDate.getTime();
    var daysPassed = java.lang.Math.floor(diffMs / (1000 * 60 * 60 * 24));
    execution.setVariable('daysPassed', daysPassed);

    // Is it more than 3 days old?
    var threeDaysMs = 3 * 24 * 60 * 60 * 1000;
    execution.setVariable('isExpired', diffMs > threeDaysMs);
  </script>
</scriptTask>

Why java.util.Date? Flowable's internal DateFormType uses Apache Commons FastDateFormat which only accepts java.util.Date or Calendar. Storing dates as java.time.LocalDateTime causes an IllegalArgumentException when Flowable renders date fields in task forms. The java.util.Date type is fully compatible with all Flowable APIs including timers, form rendering, and UEL expressions.

7. Task Duration / SLA Check

To route a workflow based on how long a task took (e.g., "Did the user finish within 2 hours?"), capture the start time before the task and compare at the gateway using a Script Task.

Pattern:

  1. Script Task: Capture current time into a variable.
  2. User Task: User performs the work.
  3. Script Task: Compare current time vs captured start, set boolean.
  4. Gateway: Branch on the boolean.
<!-- 1. Mark Start Time -->
<scriptTask id="markStart" name="Mark Start" scriptFormat="super-js">
    <script>execution.setVariable('taskStartTime', new java.util.Date());</script>
</scriptTask>

<sequenceFlow sourceRef="markStart" targetRef="userTask"/>

<!-- 2. The Task -->
<userTask id="userTask" name="Perform Work" flowable:assignee="${initiator}"/>

<sequenceFlow sourceRef="userTask" targetRef="checkDuration"/>

<!-- 3. Check Duration -->
<scriptTask id="checkDuration" name="Check Duration" scriptFormat="super-js">
  <script>
    var startTime = execution.getVariable('taskStartTime');
    var elapsed = new java.util.Date().getTime() - startTime.getTime();
    // 2 hours in milliseconds
    var twoHoursMs = 2 * 60 * 60 * 1000;
    execution.setVariable('tookTooLong', elapsed > twoHoursMs);
  </script>
</scriptTask>

<sequenceFlow sourceRef="checkDuration" targetRef="checkSLA"/>

<!-- 4. Branch -->
<exclusiveGateway id="checkSLA"/>

<sequenceFlow sourceRef="checkSLA" targetRef="escalate">
    <conditionExpression xsi:type="tFormalExpression">${tookTooLong}</conditionExpression>
</sequenceFlow>

<sequenceFlow sourceRef="checkSLA" targetRef="continue">
    <conditionExpression xsi:type="tFormalExpression">${!tookTooLong}</conditionExpression>
</sequenceFlow>

Note: This measures Total Turnaround Time (from task creation to completion). Measuring strictly from "Claim Date" requires custom Java Task Listeners.