Home › Docs › Migration › Activiti

Migrate from Activiti to Priostack

Last updated: 2026-04-06 · 10 min read

Activiti uses BPMN 2.0 - the same standard as Priostack. Your process XML will load with minor attribute changes. The main migration effort is moving from Java Delegates to REST-based workers.

Concept Mapping

ActivitiPriostackNotes
ProcessEngine (Spring bean)Hosted engine (no local install)No Java/Spring required
JavaDelegateJob Worker (any language)Implement poll-activate-complete via REST
TaskListener / ExecutionListenerNot supportedModel as explicit sequence flow + Service Task instead
activiti:assignee / activiti:candidateGroupsUser Task variablesPass assignee as task variable; claim via API
ProcessDefinitionQueryGET /api/v1/process-definitionsREST equivalent: {"items":[...],"total":n}
RuntimeService.startProcessInstanceByKeyPOST /api/v1/process-instancesBody: {"bpmnProcessId":"key","variables":{}}. Answers 201 and costs 1 credit.
TaskService.complete()POST /api/v1/tasks/{id}/completeREST equivalent. Costs 1 credit on this route.
HistoryServiceInstance state endpointGET /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>
Remove all 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>
Timers are fired by the server, not by your workers: a sweep runs every 30 seconds, so a timer fires up to 30 seconds after it falls due. timeDuration and timeDate are supported; a timeCycle on an intermediate or boundary timer is refused at deploy.

Unsupported Activiti Features

Activiti featureStatus in Priostack
Embedded forms (activiti:formKey)Not supported - pass variables directly
Event SubprocessDeploys (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 / ExecutionListenerNot supported - model as Service Tasks
Activiti Explorer UIUse 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
Questions about a specific Activiti flow? Email support@priostack.com with your BPMN and the Activiti behavior you need.

← Camunda 7 Migration · Flowable Migration Guide →