Skip to content

Deviations

Stephan Pelikan edited this page Oct 11, 2026 · 23 revisions

This page lists where this adapter does not deliver what VanillaBP promises on every platform. The list is longer than for an adapter built for one BPMS, and that is the point of this adapter. We build it mock-first to find out what the Process-Engine-API would have to offer. Every entry is explained in full in GAPS.md. GAPS.md also says what would close the gap.

None of these gaps is a defect of an engine behind the API. They come from the API this adapter speaks. The first entry is not a gap at all. It says which parts of a BPMN file this adapter looks at, and several of the entries below build on it.

  1. What this adapter reads of your model
  2. Workflow modules cannot be kept apart by the engine
  3. What the engine already holds cannot be checked
  4. Only one adapter id of this type is possible
  5. Workflow awareness cannot be probed
  6. An unreachable engine looks like an unknown task
  7. The engine never reports a workflow it started itself
  8. A timer is whatever the engine behind the API makes of it
  9. The end of a workflow is not reported
  10. A changed aggregate cannot be pushed
  11. Processes have no version, only a tag
  12. Cancellations are not delivered
  13. An error nobody catches is the engine's answer
  14. A transaction subprocess is handed over unread
  15. A delivered task does not name its BPMN process
  16. A renamed process is served where the task name says so
  17. Messages are not deduplicated by the engine
  18. A start by message is not checked against the process
  19. Workflows have no observable timeline
  20. Older versions of a process cannot be checked
  21. An overdue open task is only reported
  22. Concurrent branches of a workflow are not pointed out

What this adapter reads of your model

Every other VanillaBP adapter can ask a BPMN model type which its BPMS supplies. The Process-Engine-API has none. So this adapter reads the raw XML itself, and it reads only what it needs. Everything else in a file goes to the engine byte for byte, exactly as you modelled it.

These are the parts it looks at:

  • a process marked isExecutable="true", for its id and its name;
  • the service-like tasks serviceTask, sendTask, businessRuleTask and scriptTask, for their id, the type of their zeebe:taskDefinition and a reference to a decision;
  • a userTask of the BPMN namespace, for its id, its name and the external reference of its zeebe:formDefinition;
  • the start events of a process. A start event the engine would fire on its own ends the deployment, see the engine never reports a workflow it started itself;
  • the message, signal, error and escalation declarations of the file. The mode use-prefix rewrites their names, and VanillaBP compares them with the other workflow modules of the application.

Under use-prefix the rewrite works on the same names. It also rewrites the process id and the processId of a call activity's zeebe:calledElement.

The adapter skips everything else by its local name: a compensation, a standard or multi-instance loop, a data object, an event subprocess, a gateway, a transaction subprocess. They reach the engine as you modelled them, and they run the way that engine runs them.

That is why this wiki says nothing about them. The two Camunda adapters can list what a construct becomes, because each of them talks to one known engine. This adapter talks to whatever engine you put behind the API, and the Process-Engine-API does not name it. A third such list would be guesswork, so there is none. If your model depends on one of these constructs, read that engine's own documentation and test against it.

Workflow modules cannot be kept apart by the engine

The API has no tenants, no namespaces and no scope per deployment. So the adapter rejects VanillaBP's default name-clash-avoidance mode by-adapter while deploying, and you have to choose use-prefix or none, see Configuration. use-prefix works without help from the engine. The price is that the adapter rewrites the raw BPMN XML by element name, and that covers only the dialects it knows. GAPS 15

Two applications may still end up sharing one task type. What that costs, and why a service task and a user task get different answers then, is on what happens when a task of another application reaches yours.

What the engine already holds cannot be checked

When an application starts, VanillaBP asks a BPMS which of the identifiers it is about to deploy somebody else already holds. That can be a process id of another application, or a message name of a module deployed years ago. VanillaBP warns about what it finds. You cannot ask the Process-Engine-API that, for any kind of identifier. There is nothing to search and no model to read. So this adapter says nothing, rather than half of something.

What you do get is the check which needs no engine. While a workflow module deploys, the adapter reads the message names, signal names, error codes, escalation codes and task definitions from its BPMN files. VanillaBP warns where another workflow module of the same application ends up under one of those names. This matters most under the mode none, where nothing is prefixed. And it matters most of all for a task definition. A task subscription of this API matches a task type for the whole engine. So if two modules share one task definition, the subscription of one module takes the tasks of the other. The check reads the raw BPMN by element name, so it reaches only the dialects the adapter knows. A decision id stays out of the check, because this adapter cannot prefix one either.

The BPMN process ids of what you deploy are checked too. Where the other checks warn, this one refuses: if the process ids of two workflow modules reach the engine under one id, the startup ends, see Configuration. A shared message name leaves both models in the engine. But two processes under one id mean the engine kept one model and lost the other, and no warning repairs that afterwards. GAPS 24

Only one adapter id of this type is possible

The adapter gets the engine as beans which your application provides. These beans carry no connection settings for each adapter instance. So two adapter ids of this type would talk to the very same engine. The boot fails with a message which explains what to do. This means you cannot express a migration between two engines behind the Process-Engine-API. GAPS 14

Workflow awareness cannot be probed

The correlation commands are final classes and cannot carry a PREFLIGHT_CHECK. So the adapter cannot ask whether a workflow exists. It answers "yes" without knowing, and logs one WARN which explains this. In a migration setup a wrong "yes" would send the operation to the wrong BPMS. VanillaBP does not let it come to that. This adapter reports that it cannot locate workflows. So if a workflow module prioritizes it next to another adapter, the boot ends with a message which names the ways out. Use this adapter as the only configured BPMS until the gap is closed. The same final classes are the reason why message correlation has no phase one at all. It travels with the default execution mode. GAPS 11 (why)

An unreachable engine looks like an unknown task

A failed command comes back without a type. So the adapter cannot tell a failing probe of a task or user task from an engine which is down, and both end up as "unknown". This matters only in migration setups, where the answer decides which BPMS serves the operation. GAPS 10 (why)

The engine never reports a workflow it started itself

Timer, signal and conditional start events cannot be served. Nothing in the API says "the engine just started this process". So VanillaBP could never ask your @WorkflowStartedByBpms method for the workflow aggregate, and everything after the start is addressed by that aggregate. If one of your @WorkflowService classes claims a process with such a start event, the adapter rejects it while deploying. The message names the element and the alternatives. A process which only sits in your file, marked with implemented-externally: true, is not looked at (a process nobody claims). VanillaBP also does not judge a @WorkflowStartedByBpms method which you keep for a BPMN process id the application only declares (secondaryBpmnProcesses). VanillaBP would compare it with the start events of a version the engine still holds. That model cannot be read here, so the check stays silent for such an id. GAPS 16

A timer is whatever the engine behind the API makes of it

This adapter never sees a timer fire. A timer start event is refused while deploying; that is the entry above. Every other timer sits in the model which the adapter hands to the engine. The adapter rewrites some parts of the raw BPMN to scope them: the process id, the called element of a call activity, message names, signal names, error codes, escalation codes, the type of the task definition and the external form reference of a user task. A timerEventDefinition is not in that list. It is handed over exactly as you modelled it.

So the engine has to answer the two questions a timer raises, and this adapter cannot answer either of them. First: in which time zone is a value read which names no zone? Second: how long after the due date does the workflow actually move on? On Camunda 7 both answers are written down, because that engine runs in the application's own JVM. Here they depend on the engine you put behind the API.

One shape does not depend on the answer: a value which carries its own zone or offset. A point in time which your application worked out and shared as an Instant is another, see which time zone a timer counts in.

The end of a workflow is not reported

@WorkflowEnded methods are never called. The API has no event, callback or subscription which says that an instance ended. The deployment logs a WARN which names the BPMN process. The workflow itself runs normally, which is why this does not make the deployment fail. GAPS 17

A changed aggregate cannot be pushed

aggregateChanged throws in both phases. The API changes the payload of TASKS only, and it has no command about a running process instance. Phase one already refuses. So the application sees the failure where it calls the method, and not later in an outbox dispatch after the commit. Completing a task pushes the shared values. So a task the workflow waits at is the way out. GAPS 18

Processes have no version, only a tag

The API knows no version of a deployed process definition and no order of deployments. So a version made of numbers, a range or a >/< boundary matches nothing. The adapter reports it once, with a message which explains what to do. An exact tag (version = "release-2024") works where the engine supplies the task-meta key processDefinitionVersionTag. Methods without the attribute serve every version, as everywhere else. And where the engine supplies no tag either, a delivery reaches ONLY these methods. If every method of a task names versions, the delivery fails with a message which says so. The adapter does not pick one of them. When a workflow starts, VanillaBP writes a row. That row carries the tag where the engine puts it into the answer to the start, under the same key. The API's reference adapter for an embedded Camunda 7 does not do that today, so there the row has no version. An extension which chooses what it shows by the version then learns the version from the first delivery which carries a tag. GAPS 19 GAPS 31

Cancellations are not delivered

@TaskEvent CANCELED never arrives, for service tasks and user tasks alike. The callback which ends a subscription carries the task ID but no reference to an aggregate. So the adapter cannot tell which workflow to notify.

VanillaBP has a second way for a BPMS which reports no cancellation. At every delivery the core asks the adapter whether the other tasks it believes are open in that workflow are still there. It reports the ones which are gone. That way stays closed here. The Process-Engine-API has no operation which asks about a task, and a failed command comes back without a type. So this adapter cannot tell "the engine does not have it" from "the engine did not answer". A probe which guessed would cancel the open work of every woken workflow whenever the engine has a short hiccup. Model the cancellation instead. An event subprocess or a boundary event with a service task behind it turns the moment into a delivery, and this API reports deliveries reliably. Details GAPS 27 (why)

An error nobody catches is the engine's answer

A TaskException from your handler and a cancelTask from your code both reach the API as a complete-by-error command which carries the error code. BPMN leaves open what the engine does with a code which no boundary event catches. This adapter cannot say which answer you get, because the answer belongs to the engine you put behind the API.

What this adapter does is the same either way. It sends the command. It reads the result only to find out whether the command itself got through. If it did not, the adapter logs a failed completion and the outbox retries it. Nothing comes back about whether the error was caught. And no part of VanillaBP reads your model to check that a code can be caught. So your application is not told, here as everywhere else, see where that error goes.

The two Camunda adapters answer this question on their own pages, and their answers differ. Camunda 7 ends the workflow quietly, Camunda 8 raises an incident. Find out which of the two the engine behind this API is like before a model relies on it.

A transaction subprocess is handed over unread

BPMN allows the cancel event only inside a transaction subprocess. VanillaBP has no notion of either. And transaction is one of the elements this adapter skips by name, see what this adapter reads of your model.

So a model with a transaction subprocess reaches the engine as you wrote it. What that engine makes of it is the engine's business. This adapter neither claims the construct nor refuses it, and it cannot say what happens.

Everywhere else in these wikis, the word cancel means something else, above all cancelTask and WorkflowEnd.Kind.CANCELED, see this cancel is not BPMN's cancel event.

A delivered task does not name its BPMN process

The adapter expects the engine to supply the BPMN process ID as the task meta entry bpmnProcessId. Without it, the adapter cannot route a task definition which several processes of one workflow module use. An error message explains this. The task definition then has to be unique across the processes of the module. The mode use-prefix avoids the problem, because it scopes task definitions by their process anyway. GAPS 6

A renamed process is served where the task name says so

If you rename a BPMN process, the old id stays behind in the engine, with workflows still running on it. You declare the old id with secondaryBpmnProcesses, so your methods keep serving those workflows, see a renamed process. Camunda 7 reads the old model from its engine to find the tasks it will hand out. This adapter has no repository to read it from. So it puts the names together itself, from the task definitions your @WorkflowTask methods serve for that id. Camunda 8 does the same.

Under use-prefix with task definitions scoped per process, which is the default, that is enough. The tasks of the old id carry the old id in their name. The adapter subscribes for those names, and every such delivery reaches the methods of the old id. At startup the adapter says which names it opened. It subscribes each name twice, as an asynchronous task and as a user task, because nothing outside the model says which of the two the task was.

Under none, and under use-prefix with prefix-task-definitions-per-process: false, the old and the new process share one task name. The workflows are served, but a delivery is assigned to a deployed process. Telling the two apart would need the bpmnProcessId task meta entry above, and no engine behind this API fills it. At startup the adapter says so for every id this is true of.

One case is not served. A @WorkflowTask method wired to a BPMN element id (@WorkflowTask(id = ...)) names no task definition. So no name can be put together for it, and those workflows stop at their next task. The startup warns and names the two ways out: wire the method by task definition, or keep deploying the old model under its old id until its workflows have ended.

Such an id does not get anything which needs the model. A viewer gets no BPMN diagram and no history; the adapter now says so instead of answering nothing. And none of the checks VanillaBP runs over a held version run for it. The end of such a workflow is not reported either, as everywhere else here. GAPS 23 (why)

Messages are not deduplicated by the engine

The API offers no message id. So if the outbox retries a dispatch after a crash, the message is correlated again. This concerns correlations which carry a correlation id; on other BPMS, VanillaBP lets the engine deduplicate them. Where a duplicate correlation would matter, model your catch events so that a second message does no harm. GAPS 11

A start by message is not checked against the process

startWorkflowByMessage is meant to start the process of the ProcessService you call it on. On Camunda 7 and Camunda 8, VanillaBP refuses a message which does not start that process, with an IllegalArgumentException. This adapter cannot read your models. So it cannot tell VanillaBP which messages start a process, and VanillaBP does not check here. It says so once per process when the application starts. A message which starts a different process does start it, and VanillaBP records the started workflow under the process you called. Send each process only its own messages. GAPS 30

Workflows have no observable timeline

The API has no repository and no query or history API. So the viewer gets its answers from what THIS application version deployed:

  • getProcessDefinitions reports the deployed process, versioned by its deployment key;
  • getBpmnXml returns the deployed resource byte for byte;
  • getWorkflowHistory returns the definition plus a null element history. In the SPI, null is documented as "not supported by the underlying BPMS".

This has three effects. You cannot drill into call activities. usedByElements is never filled. And a workflow which runs on a definition deployed by a PREVIOUS application version is shown with the model deployed now. GAPS 12 and GAPS 13

Older versions of a process cannot be checked

When an application starts, VanillaBP looks at the versions a BPMS still holds. It reports the task definitions of older versions which no method serves any more. This adapter reports no version at all, because the Process-Engine-API has no notion of a version (see above and GAPS.md no. 19). So the check is switched off here, and outfaded-versions has nothing to name. A dropped task definition shows up the way it did before: when a workflow reaches it. Everything else VanillaBP asks about a held version is skipped as well, because no catalog is registered: its start events, the elements which can fork one of its workflows, and how many workflows still run on it. Details

An overdue open task is only reported

A @TaskId handler can leave a task open. VanillaBP measures how long such a task has been waiting, and says so once the wait is longer than vanillabp.delivery.max-task-age, see an open task and how old it is. The API hands out a task subscription, not a lock which this adapter renews. So there is nothing to stop and no incident to raise instead. Here the report is all that happens.

Concurrent branches of a workflow are not pointed out

A BPMN process which holds more than one token has two branches which write the same workflow aggregate. Without a version attribute, one of the two writes is lost without an error. Other adapters read their model while wiring, and they let VanillaBP warn about this. This adapter never holds a model. So it reports nothing, and the hint stays silent here, for the version being deployed and for every older one the engine still runs workflows on. Everything else works. A version conflict in the transaction VanillaBP owns is recognized. A message names it and explains what to do, and it is passed on to the engine as a failed task. The four ways to avoid the collision are the same, see Two writers on one aggregate. GAPS 21