BAW uses optimistic locking per task execution: when a task is completed (or a service step finishes), its variable changes are merged into the instance's execution context inside a transaction; the engine does not lock the instance while a user has a coach open. Consequences:
- Two tasks of the same instance completed at the same time do not corrupt the data - the engine serialises the commits - but the second one can fail with a concurrency / "modified" error and is retried automatically by the engine in most cases; users see a retry message only when the retry budget is exceeded.
- Parallel branches that both hold a copy of the same business object write whole variables back: the branch that finishes last wins for that variable - not merged field by field. So two branches editing different fields of tw.local.order lose one branch's changes.
- Claiming a task (assign to me) is the only pessimistic lock: one user works on a task; others see it as claimed. It does not lock the instance data.
Design rules:
- One variable per parallel branch: give each branch its own output variable (tw.local.pricingResult, tw.local.creditResult) and merge after the join in a script; never let two active branches map into the same object.
- Shared business objects (the "shared" checkbox of a business object type) are stored once and referenced by all tasks and even other instances - use them when several tasks must see each other's updates immediately; they still write whole objects on each save, so keep them small and single-purpose.
- System of record wins: for data edited by many people over a long time, store it outside the instance (database / ECM) and let tasks read fresh and write with their own optimistic version check (a version column compared in the UPDATE).
- Idempotent integrations in parallel branches (request ids), because a concurrency retry re-runs the step.
- Collaboration: when two people must edit the same form together, use the coach collaboration feature of Process Portal (shared editing of one task) instead of two tasks on the same data.
// merge after a parallel join - explicit, field by field, so that nothing is silently lost
tw.local.order.price = tw.local.pricingResult.price;
tw.local.order.creditOk = tw.local.creditResult.approved;
tw.local.order.notes = (tw.local.order.notes || "") + tw.local.pricingResult.notes + tw.local.creditResult.notes;
-- optimistic update of a shared record in the system of record (SQL Execute Statement)
update customer set address = ?, version = version + 1 where id = ? and version = ? -- 0 rows updated -> someone else changed it: reload and let the user decide
Diagnosing: the error text mentions the variable or task and "modified" / "concurrent"; the instance's execution tree shows which tokens were active; the engine's retry attempts appear in the SystemOut / messages log as warnings. If it happens often in one place, it is nearly always two branches writing the same variable.
References