Skip to content

Configuration

Stephan Pelikan edited this page Oct 15, 2026 · 16 revisions

This page names every key the Business Cockpit reads in an application whose workflows run on Camunda 8, and what changed against version 1.

  1. Where the keys live
    The four sections the cockpit is configured in, and which of them wins.
  2. What the Camunda 8 half reads
    The keys of the cluster it listens to, all of them the adapter's own.
  3. Where the reports go
    The cockpit server's address, or the Kafka brokers, and the templates.
  4. What a workflow module tells the cockpit
    Where the module answers, how its forms are served, in which languages it reports and who names the initiator of its reports.
  5. What changed against version 1
    The keys which are gone, the keys which are new, the two settings which behave differently, and where the cockpit still waits for the cluster.

Where the keys live

Every key the Business Cockpit reads is the key version 1 read, in the place version 1 read it:

vanillabp.cockpit.<key>
vanillabp.workflow-modules.<moduleId>.cockpit.<key>
vanillabp.workflow-modules.<moduleId>.workflows.<process>.cockpit.<key>
vanillabp.workflow-modules.<moduleId>.workflows.<process>.user-tasks.<task>.cockpit.<key>

The most specific value wins per key. What a user task says beats what its workflow says, and what a workflow says beats what its workflow module says. Some settings hold for the whole application: the transport, the templates and the two switches. Those are written once at the top and exist nowhere else. initiator-source is the one exception: it stands at the top, and a workflow module or a single workflow may still answer it for itself.

VanillaBP reads nothing below vanillabp.cockpit, so the cockpit binds that part of the tree itself on both platforms. On Quarkus that binding is also what lets the application start at all, because a key below vanillabp which nothing declares ends the startup there. A misspelled key ends the Quarkus build with a message naming it and the key it was nearest to. A key of version 1 which is gone ends the build with a message saying what became of that key. Spring Boot ignores both. A workflow module's own defaults file (<moduleId>.yaml next to its BPMN files) carries these keys as well, below everything the application itself writes.

What the Camunda 8 half reads

The Camunda 8 half of the cockpit adds no key of its own. It has to know a few things about a cluster: where that cluster is, which tenant a workflow module is deployed under, how long a listener job stays locked for its worker, and how that worker polls. It reads all of that from vanillabp.adapters.<id>.*, which the VanillaBP Camunda 8 adapter owns. Configure the cluster there, and the cockpit follows.

The lock of its workers is job-timeout. The cockpit asks for it at the workflow module, and at the adapter where the module says nothing. One worker serves a job type across every process which uses it, so a lock resolved per process would be several locks for one worker.

stream-enabled, poll-interval and request-timeout are never set on a worker of the cockpit. The adapter puts them on the client it builds, every worker of that client inherits them, and an environment variable can still overrule them there. stream-timeout has no such setting on the client, so the cockpit's workers read it from the adapter's configuration themselves. Either way, an application which tuned its adapter tuned the cockpit's workers with it.

job-lease decides for the cockpit's workers too. They lease their activations where the adapter leases its own, and they have no switch of their own. Two components which lease one job type with different answers starve each other. What the lease buys, and what it costs a rolling upgrade, is explained in When a report takes longer than its lock.

The cockpit insists on one thing about your cluster, and the adapter insists on it too. The cluster has to have secondary storage (camunda.data.secondary-storage.type). A report of an event is built from the listener job and needs nothing else. But aggregateChanged and getUserTask search that storage for the workflows and the tasks of a case, and the adapter asks it which case a called process belongs to.

Where the reports go

Configure exactly one transport. Neither of them, or both of them, ends the boot with a message naming the keys of both.

These keys stand below vanillabp.cockpit.

Key What it is
user-tasks-enabled false stops user tasks from being reported at all
workflow-list-enabled false stops workflows from being reported at all
rest.base-url The address of the cockpit server's BPMS API, and with it the choice of the REST transport
rest.authentication.basic true sends a basic authentication with every report
rest.authentication.username, rest.authentication.password The user it is sent with
rest.authentication.oauth.base-url Where tokens are issued, and with it the choice of the client-credentials flow
rest.authentication.oauth.client-id, …client-secret The client the token is asked for
rest.authentication.oauth.basic true sends the client in an Authorization header instead of in the token request
rest.connect-timeout, rest.read-timeout How long connecting and answering may take, as the number of milliseconds version 1 expected or written with a unit (1500ms, PT20S); 1.5 and 10 seconds by default
rest.proxy.host, rest.proxy.port The HTTP proxy the cockpit server is reached through
rest.proxy.username, rest.proxy.password The user that proxy expects, where it expects one
rest.verify-ssl false switches the certificate check off altogether
rest.ssl-truststore-filename, rest.ssl-truststore-password The PKCS12 file the server's certificate is checked against
kafka.bootstrap-servers The brokers, and with them the choice of the Kafka transport
kafka.topics.user-task The topic user-task reports go to
kafka.topics.workflow The topic workflow reports go to
kafka.topics.workflow-module The topic a workflow module registers itself on
kafka.properties.<name> Any further producer setting, handed to the client unchanged
template-loader-path The directory the title templates are loaded from

The client-credentials flow configures its own connection, the way it did in version 1. connect-timeout, read-timeout, verify-ssl, ssl-truststore-filename, ssl-truststore-password and proxy.* below rest.authentication.oauth describe the way to the authorization server. They inherit nothing from rest.*.

No key switches a request log on. The REST client logs through SLF4J, so set the logger io.vanillabp.cockpit.bpms.api.v1_1.BpmsApi to DEBUG to see what is sent.

What a workflow module tells the cockpit

These stand below vanillabp.workflow-modules.<moduleId>.cockpit, because that is what they are about.

Key What it is
workflow-module-uri Where the module answers the cockpit's provider APIs
ui-uri-type A name agreed with your cockpit user interface; VanillaBP knows none
ui-uri-path Where the module's forms are served from
i18n-languages The languages titles are reported in, written as a list
bpmn-description-language The language the names in the BPMN files are written in; mandatory without templates
group-hierarchy.<group> Which groups a group stands for, written as a list
template-path The segment this module contributes to the template lookup path
initiator-source Who names the user who caused a report: by-application or system. No default

A gap is reported at startup, and every gap of the application is reported in one boot, each line naming the key which fixes it. A workflow module which writes none of these keys reports nothing to the cockpit, and the log says so while the application starts.

initiator-source is the one key here which has no default. A module which reports to the cockpit and answers it at no level ends the boot. Write by-application where a @WorkflowDetailsProvider or @UserTaskDetailsProvider method of yours sets the initiator from what the workflow aggregate knows about the user, and system where the module knows no action a user causes. Camunda 8 cannot answer it for you. A listener job does not carry the user who started an instance, and the searchable storage does not hold that user either. So this adapter prefills nothing here. An application which answers the same way for all of its modules writes it once at vanillabp.cockpit.initiator-source.

Four of these mean something for a single workflow as well, and one of them for a single user task. Both levels have a cockpit section of their own, and the most specific value wins per key:

vanillabp:
  workflow-modules:
    taxi-ride:
      cockpit:
        initiator-source: by-application
        i18n-languages:
          - en
          - de
        group-hierarchy:
          TEAM_LEAD:
            - TEAM_MEMBER
            - ASSISTANT
      workflows:
        TaxiRide:
          cockpit:
            i18n-languages:
              - fr
            template-path: rides
          user-tasks:
            approve:
              cockpit:
                template-path: approval
Key What it is
workflows.<process>.cockpit.i18n-languages The languages this workflow's titles are reported in
workflows.<process>.cockpit.bpmn-description-language The language this workflow's BPMN names are written in
workflows.<process>.cockpit.template-path The segment this workflow contributes to the template lookup path
workflows.<process>.user-tasks.<task>.cockpit.template-path The segment this user task contributes
workflows.<process>.cockpit.initiator-source Who names the initiator of this one workflow's reports

The module still says i18n-languages and bpmn-description-language itself. A workflow which disagrees overrules it for its own reports. initiator-source works the same way, so a module whose cases are started by users can still say system for the one workflow a nightly job starts.

What changed against version 1

Every key not named here is the key it was, in the place it was. These are gone, one reason each:

Version 1 key Why it is gone
vanillabp.cockpit.rest.log the client logs through SLF4J, so set the logger io.vanillabp.cockpit.bpms.api.v1_1.BpmsApi to DEBUG
vanillabp.cockpit.rest.additional-get-parameters.<name> it appended query parameters to GET requests, and every report is a POST
vanillabp.cockpit.rest.retry.enabled, .max-attempts, .period, .max-period a failed report waits in the outbox and is repeated from there, which vanillabp.outbox.* configures
vanillabp.cockpit.kafka.group-id-suffix it made a consumer group unique, and the adapter only produces
vanillabp.cockpit.jwt.hmacSHA256-base64, vanillabp.cockpit.jwt.cookie.* version 1 bound them and read them nowhere, and the cockpit server configures its own tokens
vanillabp.workflow-modules.<id>.cockpit.group-hierarchy-bean-name the hierarchy is configuration, and which groups may see a module is answered by the WorkflowModuleDetailsProvider bean
workerId it named the instance a report came from, and the adapter takes the host name for that
spring.application.name only the Camunda 8 exporter path read it, and that path is gone
spring.kafka.consumer.*, camunda.zeebe.kafka-exporter.topic-name version 1 also consumed a topic a Camunda 8 exporter wrote, and version 2 learns the same from the listeners it puts into the models
io.vanillabp.businesscockpit.tasklistener.prefixes, io.vanillabp.businesscockpit.executionlistener.prefixes, io.vanillabp.deployment.priority entries of an in-memory map of version 1's deployment rather than keys of a configuration file; a job type of this adapter is recognized by its own prefix

Three keys are not version 1's. vanillabp.cockpit.kafka.bootstrap-servers and vanillabp.cockpit.kafka.properties.* are what version 1 took from spring.kafka.bootstrap-servers and spring.kafka.producer.*. The cockpit now builds its own producer, on both platforms. vanillabp.cockpit.process-engine-api.remembered-user-tasks belongs to the cockpit's Process-Engine-API half and does nothing in an application running on Camunda 8.

The keys the cockpit had of its own below vanillabp.workflow-modules.<id>.adapters.camunda8 are the Camunda 8 adapter's now:

Version 1 key What reads it now
use-tenants vanillabp.adapters.<id>.name-clash-avoidance, whose default by-adapter is what use-tenants: true was
tenant-id vanillabp.adapters.<id>.tenant-id; unset, the tenant is the workflow module id, as in version 1
task-timeout (per workflow module, per workflow, per task) vanillabp.adapters.<id>.job-timeout, resolved at the same levels; the cockpit's workers ask for it at the workflow module, because one worker serves a job type for every process which uses it
poll-interval (same three levels) vanillabp.adapters.<id>.poll-interval, per adapter rather than per workflow or task
poll-request-timeout (same three levels) vanillabp.adapters.<id>.request-timeout, per adapter
stream-enabled (same three levels) vanillabp.adapters.<id>.stream-enabled, per adapter
stream-timeout (same three levels) vanillabp.adapters.<id>.stream-timeout, per adapter
workflow-visibility-timeout nothing of the cockpit's own; the waits which are left read the adapter's vanillabp.adapters.<id>.workflow-visibility-timeout, see below

Those five worker keys were the cockpit's own copy of what the Camunda 8 adapter already configured. A worker of the cockpit is a worker on the same cluster, so it is opened the way the adapter opens its own. The three finer levels the four poll and stream keys had are gone with them. Those settings describe a connection rather than a task, and the adapter keeps them per adapter id.

Two things behave differently although they are spelled the same. The timeouts accept the number of milliseconds version 1 expected, and they accept the spelling both platforms use; their defaults are version 1's, 1.5 and 10 seconds. And every setting is read and checked while the application starts, rather than when the first event arrives, so the log of the first boot is what you work from.

workflow-visibility-timeout said how long version 1 would keep asking the cluster for a workflow it had just been told about, inside the transaction of the caller. That key is gone, and so is that waiting. Version 2 does not ask in the caller's transaction. A change reported for a workflow the cluster does not know yet is built when the outbox sends it. It is sent again a little later while the cluster still does not know the workflow.

Where the cockpit still waits

The cockpit still waits for the cluster in two places. Both wait for the cluster's secondary storage, which runs a little behind the engine. Both read the Camunda 8 adapter's key of the same name, vanillabp.adapters.<id>.workflow-visibility-timeout. It is ten seconds by default, and zero switches the waiting off. The key belongs to the VanillaBP Camunda 8 adapter, which waits that long for the same storage. So you set it once, for both.

Line When it waits What it waits for
8.8 in every listener job, and for every task read from the storage the root of the call hierarchy, because the job does not name it
8.8, 8.9, 8.10 for a called process which shares its workflow aggregate with another process the chain of calls above it, which says the case it belongs to

On 8.8 a job does not say whether another process called its workflow. So the cockpit asks the cluster for the call hierarchy of the job's process instance. It asks once per instance and remembers the answer. It waits only while the storage does not know the instance at all. That can happen to any workflow which started a moment ago, called or not. If the time runs out, the cockpit treats the workflow as a case of its own, and the log says so. On 8.9 and 8.10 the job names its root, so nothing is asked here.

On every line, a called process which shares its workflow aggregate with another process needs the chain of calls above it. That chain says which case the process belongs to, see A called process. A workflow nobody called and a called process with an aggregate of its own cost no request. If the time runs out, the report goes to the top of the hierarchy, and the log says so. On 8.8 this wait is normally short, because the first one already waited for the same storage.

Both waits happen inside a listener job. The transition waits for them, and the time counts against the lock of the job, see When a report takes longer than its lock. They also happen when a report is built from the storage. That is when the outbox sends a change you reported, and when getUserTask answers. They never happen in the transaction of aggregateChanged. Set the key to zero where a transition must not wait. Then the cockpit asks once and takes the answer it gets. Why line 8.8 pays more is decision 7. Why a called process may be a case of its own is decision 3. The lines are listed in the README of the repository.

What else changes for an application coming from version 1 is on Migrating from Version 1.