Migrate from Activiti to Priostack
Last updated: 2026-04-06 · 10 min read
Concept Mapping
| Activiti | Priostack | Notes |
|---|---|---|
| ProcessEngine (Spring bean) | Hosted engine (no local install) | No Java/Spring required |
| JavaDelegate | Job Worker (any language) | Implement poll-activate-complete via REST |
| TaskListener / ExecutionListener | Not supported | Model as explicit sequence flow + Service Task instead |
| activiti:assignee / activiti:candidateGroups | User Task variables | Pass assignee as task variable; claim via API |
| ProcessDefinitionQuery | GET /api/v1/process-definitions | REST equivalent: {"items":[...],"total":n} |
| RuntimeService.startProcessInstanceByKey | POST /api/v1/process-instances | Body: {"bpmnProcessId":"key","variables":{}}. Answers 201 and costs 1 credit. |
| TaskService.complete() | POST /api/v1/tasks/{id}/complete | REST equivalent. Costs 1 credit on this route. |
| HistoryService | Instance state endpoint | GET /api/v1/process-instances/{key} |
BPMN Namespace Changes
Activiti uses the activiti: namespace for extensions. Replace with zeebe:. The snippets below are fragments: paste them inside a <definitions> element that declares xmlns:zeebe="http://camunda.org/schema/zeebe/1.0".
Before (Activiti):
<serviceTask id="send-email" name="Send Email"
activiti:class="com.example.SendEmailDelegate">
<extensionElements>
<activiti:field name="to" expression="${order.email}" />
</extensionElements>
</serviceTask>
After (Priostack):
<serviceTask id="send-email" name="Send Email">
<extensionElements>
<zeebe:taskDefinition type="send-email" />
</extensionElements>
</serviceTask>
activiti:class, activiti:expression, activiti:delegateExpression attributes. Priostack ignores them, so they do nothing, and a service task without zeebe:taskDefinition gets its job type from its name, then from its id. Field injection (activiti:field) has no equivalent, and zeebe:ioMapping is not read either: every job carries all of the instance's variables, so the worker reads order.email itself.
Migration Steps
Step 1 - Export all process and form definitions
Export .bpmn20.xml or .bpmn files from your Activiti workspace. If you use Activiti Designer (Eclipse plugin) or Activiti Modeler, export using "Save As BPMN 2.0 XML".
Step 2 - Strip Java delegate references
Use find-and-replace to remove Activiti-specific attributes. The key patterns to remove or replace:
# Patterns to remove / replace in your .bpmn files: activiti:class="..." → remove (replace task with zeebe:taskDefinition) activiti:expression="..." → remove activiti:formKey="..." → remove (use task variables instead) activiti:assignee="..." → remove (set via variable in worker output) activiti:candidateGroups="..." → remove
Step 3 - Deploy to Priostack
curl -X POST "https://priostack.com/api/v1/process-definitions?resourceName=order-process.bpmn" \ -H "X-API-Key: ps_your_key" \ -H "Content-Type: application/xml" \ --data-binary @order-process.bpmn
This route deploys BPMN only and is free. It answers 200 with a deployments array; a model that does not parse answers 422.
Step 4 - Implement REST workers (replaces JavaDelegate)
Each JavaDelegate class becomes a polling worker. Worker in Node.js example:
const headers = { 'X-API-Key': 'ps_...', 'Content-Type': 'application/json' };
// poll for jobs of type "send-email" (the answer comes back at once)
const resp = await fetch('https://priostack.com/api/v1/jobs/activate', {
method: 'POST',
headers,
body: JSON.stringify({ type: 'send-email', worker: 'mailer-1', maxJobsToActivate: 5 })
});
if (!resp.ok) throw new Error(`activate failed: ${resp.status}`);
const { jobs } = await resp.json(); // [] when nothing waits: sleep 1 to 5 s, then poll again
for (const job of jobs) {
const to = job.variables.order.email; // every job carries all instance variables
await sendEmail(to); // your business logic
const done = await fetch(`https://priostack.com/api/v1/jobs/${job.key}/complete`, {
method: 'POST',
headers,
body: JSON.stringify({ variables: { emailSent: true } })
});
if (!done.ok) throw new Error(`complete ${job.key} failed: ${done.status}`); // success is 204
}
Step 5 - Handle User Tasks
Activiti's Tasklist UI is replaced by Priostack's /tasklist or your own UI calling the tasks API:
# Get open tasks for a user
GET /api/v1/tasks
# Complete a task (1 credit on this route)
POST /api/v1/tasks/{taskId}/complete
Content-Type: application/json
{"variables":{"approved":true,"reviewNote":"Looks good"}}
Step 6 - Remove Spring ProcessEngine wiring
Remove camunda-bpm-spring-boot-starter or activiti-spring from your pom.xml/build.gradle. Your workers only need an HTTP client.
Timer / Boundary Events
Activiti timer events use ISO 8601 duration syntax - the same format Priostack expects:
<!-- 3-day SLA timer - same syntax in both engines -->
<boundaryEvent id="sla-timer" attachedToRef="review-task" cancelActivity="true">
<timerEventDefinition>
<timeDuration>PT72H</timeDuration>
</timerEventDefinition>
</boundaryEvent>
timeDuration and timeDate are supported; a timeCycle on an intermediate or boundary timer is refused at deploy.
Unsupported Activiti Features
| Activiti feature | Status in Priostack |
|---|---|
| Embedded forms (activiti:formKey) | Not supported - pass variables directly |
| Event Subprocess | Deploys (triggeredByEvent="true"), but on the hosted service only a timer start event fires one today, from the server's 30-second timer sweep. Error and escalation starts never fire, because nothing on the hosted service raises an error or an escalation (no job call throws one, and an error end event is a plain end). Message and signal starts have no correlation or broadcast call. Do not use a compensation start: a process carrying one fails to start an instance today; model the undo with compensation handlers instead. When the handler fires it is offered to workers as one job of type subProcess:<id>; the elements drawn inside it are not run by the engine. |
| TaskListener / ExecutionListener | Not supported - model as Service Tasks |
| Activiti Explorer UI | Use Priostack Dashboard + Tasklist instead |
| Database-backed history (ACT_HI_* tables) | No history database. Instances are saved to a state file and survive a restart; GET /api/v1/process-instances/{key} returns each instance with its elementHistory, kept until the account is deleted |