How Flowable service tasks call Java code
Flowable service tasks call Java code through three attributes on the task: flowable:class, flowable:expression and flowable:delegateExpression. A team whose BPMN process must reach existing Java services, such as a pricing library, a customer record API or a payments client already running inside the application, needs to choose among these three options before modeling starts, because each one decides how the engine creates the object it calls, when that object is instantiated and how it can be replaced in tests. The engine itself is embedded or reached over HTTP, and the Java side is an ordinary application class that the engine invokes when a token reaches the task.
At runtime, the engine resolves the reference when the token arrives at the service task. With flowable:class, Flowable instantiates the named class for each execution, so the class needs a public no-argument constructor and any injected fields are set through field injection. With flowable:expression, the engine evaluates a UEL expression that usually calls a bean by name in the surrounding container, and the expression result is used as the task's return value rather than as a class to run. With flowable:delegateExpression, the engine resolves a bean that implements JavaDelegate (or ActivityBehavior) from the configured bean registry, which is the option most Spring-based teams use because the container controls lifecycle and dependencies. The JavaDelegate interface has one method, execute, which receives the DelegateExecution carrying process variables and the activity context.
Confirm three things before you commit to a pattern: which bean registry your deployment uses (plain Java SE, a servlet container or Spring), whether the services your delegates call are safe to run inside the engine's transaction, and whether the team expects to change the bound object without redeploying the process model. The Flowable getting started guide explains the embedding options that determine which bean registry is available, and the Flowable open source page covers the engines these delegates run on. Check the exact field and expression syntax against the documentation for your Flowable version, since the Java 17 and Spring Boot baseline changed across major releases.
Which binding option fits your service task
This table compares the ways a Flowable service task can reach Java code and the add-ons that extend it. Read each row as a decision: the situation where the option fits and the trade-off you accept. In most projects, delegateExpression with a Spring bean is the default for reusable logic, while a plain class reference suits small, self-contained delegates.
| Option | Fits when | Main trade-off |
|---|---|---|
| class attribute | A self-contained delegate with no Spring beans or container lookups | Flowable instantiates the class, so constructor injection and bean wiring are not available |
| expression attribute | Calling a bean method or a simple value expression with arguments | Logic in expressions is hard to unit test and easy to scatter across models |
| delegateExpression attribute | Reusable Java logic resolved as a bean from the application context | The bean name becomes a runtime dependency that must exist in every environment |
| Field injection | Passing fixed strings or small configuration values into a JavaDelegate | Fields are set per model, so changes to a shared value mean editing every process |
| Triggerable service task | The step should run on demand, such as when an external system signals completion | Adds a design step to plan the trigger, correlation and failure paths |
| Execution listeners | Cross-cutting actions such as logging or setting a variable on start or end | Hidden behavior is harder to trace from the diagram than an explicit task |
| Calling Flowable services in a delegate | The delegate needs to start processes, read variables or query tasks | Tight coupling to the engine API makes the delegate harder to move or replace |
Trade-offs in delegate design
A delegate is small code with large reach: it runs inside the engine's transaction, is created by the engine or Spring, and is reached by name from the model. The cards below cover the choices that shape how that code is built, tested and maintained, and what each choice costs your team over time.
Instance per execution
Flowable creates a new instance of a class-based delegate for each execution that reaches the task, unless you use a bean reference. Stateful fields on a delegate therefore do not persist between steps, and any state must live in process variables or an external store. Check whether the delegate holds fields, and whether a developer might later add a cache or counter that silently behaves differently per execution.
Spring bean lifecycle
Using delegateExpression with a Spring bean ties the delegate to the application context. The bean's scope, singleton or prototype, decides whether state is shared across concurrent executions, and a bean that depends on a request-scoped or transactional resource can fail only when an async job runs. Confirm the bean scope and its dependencies, and test a job executed by the async executor, not only a synchronous call.
Transaction and async boundaries
A service task runs in the same transaction as the surrounding wait state unless the model adds flowable:async. A failure after an external call such as a payment or email can roll back process state while the external side effect has already happened. Decide where commits occur, make external calls idempotent, and document which steps can repeat after a retry.
Reuse across processes
A shared delegate reduces duplicate logic, but every process that references it inherits its field names, variable contracts and error behavior. Changing a field injection or an expression in one delegate can break processes deployed on older versions that still run. Keep a short list of variables each shared delegate reads and writes, and review it whenever the delegate changes.
Testability of delegates
A delegate that calls the runtime service directly is hard to test without an engine. Passing inputs through variables and keeping the call to the engine in one place makes unit tests practical, while integration tests still need a running engine with the real database. Ask your team who will maintain the test harness, and budget for it as ongoing work, not a one-time setup.
Where business logic belongs
Rules that change often, such as pricing thresholds or eligibility criteria, are usually better expressed as DMN decisions or in services called by the delegate than as Java code in the delegate. Keeping the delegate thin means the process model stays readable for business analysts and changes need less redeployment. The cost is an extra component to version and monitor, so agree on the boundary before the first process is built.
How to set up a Java delegate in Flowable
This sequence takes a Java class from code to a verified service task in a running process definition. Each step closes with a check, so a failure shows up where it was introduced rather than at the end of the build.
Write the JavaDelegate class
A developer creates a class that implements the JavaDelegate interface and places the business logic in its execute method, which receives a DelegateExecution. Keep the logic free of direct database calls and read inputs from process variables with execution.getVariable. The step is done when the class compiles against the Flowable engine dependency your build uses and the method has no side effects outside the variables it sets. Confirm the interface package and method signature against the Flowable version you run, since the engine major versions differ in their Java and Spring baselines.
Register the delegate in the Spring context
If the application uses Spring, annotate the class as a Spring bean with a name, such as @Component("sendNotificationDelegate"), so the engine can resolve it through the expression or delegateExpression option. In a plain Java SE or servlet deployment, the class is referenced by its fully qualified name instead, and it must be on the classpath of the process that starts the engine. The check is that the application context starts and that the bean name or class name is listed where your team expects it, with no duplicate names across modules.
Reference the delegate from the service task
In the BPMN model, the modeler sets the service task to a Java implementation. Use delegateExpression with the bean name for Spring-managed delegates, class with the fully qualified name for plain classes, or expression for a method call on a bean. Choose one option per task so the behavior is clear to the next reader. The check is a deployment of the process definition: the engine parses the XML and reports no unresolved references. Confirm the attribute names in the Flowable BPMN documentation for your version.
Add field injection and execution listeners
Field injection passes fixed values into the delegate through field extensions in the BPMN XML, and the class declares matching fields with setters. Each injected field must be a value the engine can convert, and expressions in fields are evaluated at execution time. Execution listeners can run the same class or another one on start or end events of the activity. The check is a running instance in which the injected value reaches the delegate, confirmed by a log line or a variable the delegate writes. Treat sensitive values as secrets to be supplied by configuration, not typed into the model.
Use Flowable services inside the delegate
When the delegate needs to start a process, read a task, or query history, it obtains the engine services from the DelegateExecution context or from the Spring-injected service beans, rather than building a new engine. Calls that change state should run in the same transaction as the task so a rollback undoes them together. The check is a code review of each service call against the transaction boundary of the activity, plus a test in which a thrown exception leaves no partial change behind. Where a call must not share the transaction, the model should mark an asynchronous boundary instead.
Verify the delegate with a unit test
A developer tests the execute method in isolation with a mock or stub DelegateExecution that supplies the variables the real engine would provide. The test asserts the variables written, the exceptions thrown for invalid input and the behavior of injected fields. The step is done when the test runs in the build pipeline and fails if the delegate changes its contract. This test does not prove that the BPMN file wires the class correctly, which the next step covers. Choose the mocking approach your team already uses, and keep the test free of a live database.
Confirm the process deployment with an integration check
Deploy the process definition to a test engine and start one instance with representative variables. The check passes when the service task completes, the expected variables appear in the instance, and the history records the activity. A deployment check should also run in the pipeline so a misspelled bean name or class name fails before release. Record the Flowable version and the database type used for the test, because behavior can differ across versions and supported databases. Consider this step the release gate for the delegate.
Using Flowable services from inside a JavaDelegate
A JavaDelegate can call Flowable's runtime, task, history and identity services, and every one of those calls joins the transaction that is already running the service task. Flowable exposes these services through the process engine context, so a delegate typically reads the current execution or task, sets variables, and asks the runtime service to start or signal related work. Because the calls share the surrounding transaction, a failure later in the delegate rolls back the variable writes and engine changes made earlier in the same step. That makes the delegate a unit of work, and the boundary of that unit is set by where the service task sits relative to any asynchronous continuation in the model.
The main risk is what a delegate does with that shared transaction. Starting nested process instances, or calling a slow external system, holds database locks and an open transaction for as long as the call takes, and it can cause the job to be retried if the external call fails after the engine has already written state. Keep external I/O in a separate step or an external worker, and keep the delegate to short, engine-local work. Confirm the exact transaction behavior of each service you call against the version you run, and test a forced failure midway through a delegate to see what persists.
Identity management is the other service area to plan for. The Flowable docs say the identity service can be replaced by overriding IdmIdentityServiceImpl or by implementing the IdmIdentityService interface, and they provide an LDAP identity service implementation as a reference. If your delegates look up users or groups, decide early whether they read from the engine's own identity tables or from your directory, confirm the reference implementation's fit with your directory schema, and agree on who owns that code and its test coverage.
Security and operational risks of Java delegates
Java delegates run inside the engine's transaction with the same trust as your application code, so the engine does not check who may trigger them or what they expose. The cards below cover the risks that most often surface when service tasks call Java code, and what to verify before a delegate or expression goes to production.
Expressions evaluating untrusted input
Service task expressions such as ${...} and delegate expressions are resolved by the engine at runtime. If any part of an expression comes from process variables set by a user, an external message or an API request, that input can change which bean or method runs. Keep expression targets fixed in the model, pass only plain data through variables, and review every expression during model review, not only the Java code it calls.
Delegates bypassing application authorization
The engine leaves authorization decisions to your application. A delegate that starts, completes, or moves a process instance through the runtime service does so with full engine privileges, regardless of who triggered the step. Decide where each permission check lives, usually at the API or task layer before the engine call, and confirm that no delegate or REST call can reach an operation the user would be denied elsewhere.
Secrets in field injection
Field injection stores values directly in the BPMN XML, and that XML is deployed, versioned, exported and often copied between environments. Passwords, API tokens or connection strings written into camunda:field style fields or equivalent Flowable field extensions end up in the deployment repository and in history where definitions are visible. Pull credentials from a secrets manager or environment configuration at runtime, and keep only non-sensitive references in the model.
Logging of process variables
Variables are persisted in the database and are often logged by delegates, listeners or default error handling when a job fails. A variable holding a national identifier, health detail or payment data can therefore appear in application logs, monitoring tools and support tickets. Agree a list of sensitive variable names, mask them in logging configuration, and test a failing delegate to see what actually reaches the log output.
Failed jobs when delegates throw
When a service task delegate throws an exception, the engine rolls back the transaction back to the last committed wait state and records a failed job for the async executor to retry. Repeated retries can call external systems many times, and a failure can leave the business effect half complete in the external system. Decide which exceptions are retryable, which should map to a BPMN error, and what happens when retries are exhausted, then check the failed job queue in the operations runbook.
Side effects outside the transaction
A Java delegate that calls an external API, sends an email or writes to another database is not covered by the engine's transaction. If the process rolls back after that call, the external effect remains. Make external calls idempotent with a stable business key, record completion before continuing the process, and where a step must be undone, plan a compensating action rather than assuming rollback. Confirm this design during delegate code review before the first deployment.
Questions teams ask before building service tasks
How much effort does a set of Flowable Java delegates take to build?
Effort depends mainly on how many service tasks the process models call, how many external systems each one touches, and how much error handling and idempotency each call needs. A delegate that reads a variable and writes a log line is quick to build, while one that calls a payment or core system needs retry-safe logic, timeouts, mapping between data models and tests against sandboxes. No duration figure has been supplied for this work, so ask any partner to break the estimate down per delegate and per integration in a written scope before work starts. Flowable's open source engines are licensed under Apache 2.0, so the engine itself carries no license fee.
Can an internal team build Flowable delegates without a partner?
Yes, if the team has Java developers who know the Flowable engine APIs and the Spring or servlet container the engine runs in. Flowable is open source under Apache 2.0, and its documentation covers service tasks, expressions and listeners. Teams with no prior Flowable experience usually benefit from a short design review of the delegate boundaries, error paths and test approach before writing code. A partner can supply that review, the first delegate pattern and a handover, while your team keeps ownership of the code.
What does the work need from our developers and architects?
Developers need working Java knowledge, familiarity with the Flowable runtime and build tooling, and access to the integration endpoints the delegates call. Architects need to decide where business rules live (in BPMN, in DMN, or in Java), how delegates reach shared services, and which transaction boundaries apply. Plan for a named owner for the process definitions and one for the delegate code, because the two change at different speeds. Ask any partner which of these roles they expect you to fill and for how many hours per week.
How long does testing take when delegates call external systems?
Testing time grows with the number of external systems and with how hard they are to reach from test environments. Delegates that only use engine variables can be covered by unit tests with mocked services. Delegates that call a core system, a message broker or a third-party API need contract tests, sandbox access and tests for timeouts and duplicate calls. No testing duration has been supplied, so ask for a test plan that lists each integration, the test environment it needs and who provides access to it.
Should we use class or delegateExpression for a service task?
Use delegateExpression when you want Spring to manage the delegate as a bean, so it can receive injected dependencies and be swapped or mocked in tests. Use class for a plain Java class that the engine instantiates for each execution, which keeps the wiring simple outside Spring. Field injection suits simple constant values but should not carry secrets. Choose one pattern per application so developers and reviewers know where to look. Confirm the exact attribute behavior against the Flowable version you deploy, since configuration details can change between major releases.
How do delegates behave in a clustered Flowable deployment?
In a cluster, the engine runs asynchronous jobs and timers from a shared database. The async executor uses lock owners and lock expiration times stored in the database, so if one node fails while holding a job, the lock expires and another node picks the job up. That means a delegate can run more than once after a node failure, so delegate logic must be idempotent or guarded by a check on the external system. Keep delegates free of node-local state such as in-memory caches or files that only one node can see. Ask your architect to confirm the lock and retry settings for your database and version.
What happens to running instances when a delegate changes?
Running instances keep their current process definition, and the engine resolves service task classes and expressions when the token reaches that step. A changed delegate class therefore affects instances that have not yet reached the task, and it can break instances that are waiting at it if the variables or signatures no longer match. Deploy changes with a compatibility plan: keep old classes available until old instances finish, or migrate instances to a new definition version. Confirm the migration and version behavior for your Flowable release before changing a delegate that has active instances.
What Flowable version changes mean for delegates
Before you write or port a Java delegate, fix the Flowable version and the Java, Spring and Jakarta baseline it requires, because those choices decide which APIs your delegate code may import. According to the Flowable 7.0.0 release notes, version 7 moved to Java 17, Spring Boot 3 and Jakarta EE, and removed the UI applications, the form and content engines, Mule integration and async history. A delegate that depends on javax.* servlet or persistence types, or on a removed component, will not compile or run unchanged on that line. Confirm the minimum Java version and the Jakarta namespace in the release notes before you assume an older delegate class can be reused.
The 8.0.0 release, dated February 27, 2026 in the flowable-engine GitHub releases page, goes further. Its release notes describe an upgrade to Spring Framework 7 and Spring Boot 4, with no backward compatibility for Spring Boot 3, and make Jackson 3 the default JSON library with a Jackson 2 compatibility layer. Any delegate that calls Spring beans, reads @Value or @Autowired wiring from a Spring 6 context, serializes variables with Jackson, or uses servlet-facing classes must be reviewed against those changes. Check each delegate's imports and each delegateExpression bean name, since an expression that resolves to a bean from a different Spring generation fails at runtime rather than at deployment.
Treat the upgrade as a code review item with a test gate, not a version bump. Build an inventory of every JavaDelegate, ActivityBehavior, listener and expression-bound bean, then run each one against the target engine with a process test that exercises its variable reads and writes. Ask your delegate authors, or a partner doing the work, to confirm in writing which Spring, Jackson and JUnit versions the tests use, since the 8.0.0 notes also remove JUnit 3 and 4 support. Record the baseline you chose in the project's build file so the decision is visible when the next upgrade is planned.
Limits to confirm in your environment
Several behaviors of Flowable service tasks depend on the engine version, the expression language, the async executor and your deployment layout. The cards below list what to confirm for your version and where to confirm it, so the design does not rest on assumptions.
Expression language behavior by version
Expressions in expression and delegateExpression attributes are evaluated by the engine's expression support, and the rules for resolving bean names, method calls and null values can differ between releases. Test each expression form you plan to use, including field injection values, against the exact engine version you will deploy. Confirm the behavior in the BPMN and expression sections of the Flowable docs for that version, and do not rely on examples from older tutorials.
Interaction with async continuations
A service task marked flowable:async runs in a separate transaction, and whether a delegate's changes commit before or after a failure depends on where the continuation sits. Retries, exclusive jobs and the transaction boundary all affect what a delegate sees when it runs. Confirm with the Flowable advanced docs how the async executor schedules these jobs in your version, and test a failure midway through a delegate to see what persists.
Class and bean discovery in deployment
A class attribute needs the delegate on the runtime classpath, while a delegateExpression needs a bean the engine can resolve from its configured context. In a Spring Boot or servlet deployment, these lookups can fail if the delegate sits in a module that is not packaged, or if the bean name differs between environments. Confirm the lookup rules for your embedding style, and check the failure mode when a delegate is missing, since it may surface only when the task executes.
Identity and service access from delegates
A delegate that calls Flowable services, such as the runtime or task services, runs with the same engine configuration as the process, but any identity or authorization checks belong to your application. If the delegate reads users or groups, confirm how the identity service is wired in your deployment, since the docs describe replacing the default identity service with your own implementation. Test the delegate under the identity configuration you will run in production.
Performance under concurrency
Delegates run on the same threads and database connections as the async executor, so slow external calls or heavy service usage can hold locks and delay other jobs. Under concurrent load, confirm how many jobs your executor runs at once, how lock expiration recovers a stalled job, and whether your delegate's database work is safe for repeated execution. Load test with realistic process volumes in a staging environment before you commit to a design.
How DeployOpen scopes delegate work
Scoping starts with a written inventory of every service task in the process models that will call Java code, the systems each one reaches, and the failure behavior you expect when those systems are slow, unavailable or return unexpected data. Without that list, a quote cannot tell apart a delegate that reads one database row from one that opens a payment connection, writes an audit record and calls a third-party API inside the same transaction. For each task, record which mechanism it uses (class, expression or delegateExpression), which variables it reads and writes, whether it runs asynchronously, and whether a failure should retry, raise a BPMN error or stop in a deadletter job. Those answers drive most of the design work and most of the testing.
Engagements of this kind are usually fixed-scope. DeployOpen agrees the scope, deliverables and the conditions for accepting the work in writing before implementation starts, and the quote follows from that document rather than from a generic rate. Ask for the quote to list each delegate or expression as a separate line with its external dependencies, its test expectations and any identity or credential handling it needs. Confirm which Flowable version and Java baseline the delegates must target, since the engine's supported runtime changes between major releases. Also confirm who maintains the delegates after handover, whether ongoing support is needed as a separate retainer, and which environments (your cloud accounts, data centers or air-gapped networks) the code must run in.
How DeployOpen delivers Flowable delegate work
DeployOpen plans, implements, tests, documents and supports Flowable engines inside your own infrastructure, including the Java code that service tasks call. Delegate work is scoped in writing before it starts, so the expressions, injected fields and service calls your processes depend on are agreed with your team up front.
How we scope
- Inventory every service task and classify it as class, expression, delegateExpression or triggerable
- Agree in writing which Java classes, Spring beans and external services each task may call
- Define which process variables each delegate reads and writes, and the error cases it must handle
- Confirm the engine version, Java baseline and Spring stack the delegates must compile and run against
- Engagements are fixed-scope, with deliverables and timelines agreed before work starts
How we build
- Implement JavaDelegate and bean-based delegates with field injection kept to reviewed, documented values
- Use delegateExpression for Spring-managed beans where your architecture already relies on dependency injection
- Add execution listeners and triggerable service tasks only where the scope requires them
- Call Flowable services from inside delegates with transaction boundaries reviewed against the async configuration
- Keep external calls idempotent and tested against timeouts and retries
How we hand over
- Unit tests for each delegate, plus process-level tests that run the service task path end to end
- Documentation of each task's inputs, outputs, expressions and failure behavior for your engineers
- Code review notes covering expression injection risk and which values come from deployment configuration
- Runbook entries for failed jobs, retries and the delegate errors your operations team will see
- Knowledge transfer sessions with the engineers who will maintain the delegates after handover
How we support
- Ongoing support, upgrades and continuous testing are available as retainers
- Re-test delegates when the Flowable engine version or Spring baseline changes
- Review new service tasks against the agreed scope before they reach production
- Track deprecations in the Flowable release notes that affect delegate APIs or Spring integration
- Scope any change to a delegate or expression as a written change to the original agreement

