A BPMN 2.0 order process puts a non-interrupting 24-hour reminder timer on 'Await payment', and its branch rejoins the main flow before 'Ship order'; why do orders now ship twice?
answer
- count the tokens
- dashed border keeps the task alive
- merge passes every token
- the spec's advice for such branches
basics
~20 sIn BPMN 2.0 a non-interrupting boundary event adds a second token while the task keeps waiting. Merged into the main flow, that token ships the order once after the reminder and again after payment. End the reminder branch with its own end event.
solid answer
~50 sThe dashed timer is **non-interrupting**: at 24 hours it creates a **second token** on the reminder branch while "Await payment" stays active with the first. Rejoining the main flow through an exclusive merge, or straight into "Ship order", passes each token through, so the reminder token ships an unpaid order and the payment token ships it again. The BPMN 2.0 specification warns about exactly this: because the boundary token runs in parallel with the continuing activity, care must be taken when merging it, and typically the branch should end with **its own end event**. So the fix is "Send reminder" followed by an end event. A parallel join is the wrong fix: when payment arrives within 24 hours the timer never fires and the join waits forever. Making the timer interrupting is wrong too, because it cancels the wait.
go deeper
Recall that a dashed boundary event does not stop the task, so something extra starts running alongside it.
Explain the two tokens after the timer fires and why a merge lets both reach 'Ship order'.
Diagnose by counting tokens for zero, one and many firings, reject the join and interrupting fixes with reasons, and end the reminder branch on its own end event.
Make token-count review of every non-interrupting branch part of the modelling checklist, since this defect only surfaces for slow payers in production.
## The model as drawn An order-fulfilment process in BPMN 2.0 (OMG formal/13-12-09) has a receive task **"Await payment"**. Someone added a **non-interrupting timer boundary event** (dashed border, `cancelActivity="false"`, duration 24 hours) that leads to a task **"Send payment reminder"**. To keep the diagram tidy, the reminder branch was drawn back into the main line: its sequence flow enters an exclusive merge just before **"Ship order"**, or connects to "Ship order" directly. Since the change, orders whose payment took longer than a day are shipped twice, and some unpaid orders are shipped once. ## Counting the tokens The diagnosis is a token count, which is how BPMN defines behaviour. 1. The order arrives at "Await payment" with **one token**. 2. After 24 hours the non-interrupting timer fires. The activity **continues to be active**, and a **new token** is generated on the timer's outgoing sequence flow. There are now **two tokens** for this order. 3. The reminder token runs "Send payment reminder" and reaches the merge. An exclusive merge passes every token that arrives, and several sequence flows entering an activity directly behave the same way (uncontrolled flow). So "Ship order" runs for the **unpaid** order. 4. Later the payment message arrives, "Await payment" completes, and its token also reaches "Ship order". The order ships a **second** time. If payment never arrives, the first shipment already happened, which is the unpaid-order symptom. If payment arrives within 24 hours, the timer is no longer listening, because new non-interrupting handlers can only be created while the activity is Active. That is why the fault shows only on slow payers. ## What the specification says The specification anticipates this. For non-interrupting boundary events, it says the activity continues to be active and, because a token is generated for the boundary flow in parallel to the continuing activity, **care MUST be taken when this flow is merged into the main flow of the process; typically it should be ended with its own end event**. ## Fixes, right and wrong | Proposed fix | Result | |---|---| | End "Send payment reminder" with its own end event | Correct: the reminder token is consumed; only the payment token reaches "Ship order" | | Replace the merge with a parallel join | Wrong: a join waits for both tokens, so when payment arrives inside 24 hours the second token never comes and the instance never completes | | Make the timer interrupting | Wrong: the wait is cancelled at 24 hours, so the payment can no longer be received | | Loop the reminder branch back into "Await payment" | Wrong: a second instance of the task starts while the first is still waiting, doubling the wait and everything after it | | End the reminder branch with a terminate end event | Wrong: it ends every active activity in the process, including the wait for payment | The model the business actually wants usually has **two** timers on "Await payment": - a **non-interrupting** 24-hour timer, branch "Send payment reminder" then a none end event; - an **interrupting** 48-hour timer, exception flow "Cancel order", which ends the wait for good. ## Related traps in the same place - **Cycle timers.** If the reminder timer uses a `timeCycle` (for example the ISO 8601 recurrence `R/PT24H`) instead of a `timeDuration`, it can fire every 24 hours while the task is active, and each firing adds another token. With the faulty merge, every reminder is another shipment. - **Multiple non-interrupting events.** A non-interrupting message event, such as "Address changed", has the same shape: each occurrence adds a token, so its branch must end on its own too. - **Process completion.** A process instance completes only when no token remains. A reminder branch ending in its own end event is fine: its token is consumed there, and the instance completes when the main path ends. ## How to spot it in review - For every **dashed** boundary event, follow its branch to where it ends. If it reaches a node the main flow also reaches, count tokens. - Ask what happens when the event fires **zero times, once, and several times**. A correct branch behaves the same in all three cases. - Treat a merge or join fed by a non-interrupting branch as a smell by default.
- In BPMN 2.0, why does replacing the exclusive merge with a parallel join make this model worse?A parallel join waits for a token on every incoming flow. Orders paid within 24 hours never fire the timer, so the second token never arrives and the join waits forever; the instance cannot complete. Orders paid late ship once but only after the reminder, and a cycle timer would still produce surplus tokens.
- In BPMN 2.0, how should the model express 'remind after one day, give up after two'?Attach two timers to 'Await payment'. A non-interrupting 24-hour timer leads to 'Send payment reminder' and its own end event; an interrupting 48-hour timer cancels the wait and follows its exception flow to 'Cancel order'. Payment before 48 hours completes the task normally and disarms both timers.
saying these in an interview costs you the question
- A non-interrupting branch can safely rejoin the main flow through an exclusive merge.
- A parallel join fixes the duplicate by waiting for both branches.
- Making the timer interrupting keeps the reminder and removes the duplicate.
- A boundary timer keeps firing after its task has completed.
- A duplicate shipment here must be an engine bug, not a modelling error.