Flowable JPA entity variables: when a reference beats a copy
Teams that already keep customer, order or case records in JPA entities and want a Flowable process to act on them usually face one design choice: copy the values into process variables, store only the entity's ID, or store a reference to the entity itself. The Flowable JPA documentation describes the third option. It stores a reference to the JPA entity as a process variable and loads the entity when a delegate, expression or listener needs it. Queries on JPA process variables are also supported, so the process can be found by the entity it is working on. This matters most when the process must see the current state of a record at the moment it acts, not the state from when the process started.
A reference is the stronger choice when several steps read or change the same record, when the record can be edited outside the process, or when a stale copy would lead to a wrong decision, such as approving a loan against an old balance. A copy is simpler when the process needs a fixed snapshot for audit, because the history then shows exactly what the decision was based on. Storing only the ID sits between the two and works well when the entity is large or its loading rules are complex. Before choosing, confirm with your team which of these cases applies to each variable, since one process often needs all three.
Before you adopt references, check three things in your own environment. First, confirm how the entity is serialized into the variable store and that the entity class and its JPA mapping are stable across the release cycle, because in-flight instances must still load it after a deployment. Second, decide which Spring transaction owns the load and save, and test that a failed step rolls back both the process state and the entity change. Third, check the Flowable version you target against the Flowable JPA docs, since the example and the supported integration details are documented there and should be verified before you build on them.
Reference, copied value or plain variable: which option fits
This table compares storing a JPA entity reference, copying the fields the process needs into plain variables, and storing only the entity ID for a delegate to load. Read each row as a question you must answer for your process, then use the column that matches your answers. The main takeaway is that references give the freshest data at the cost of persistence coupling and a larger audit footprint, while copied values and IDs keep the process state stable and easier to review.
| Factor | Entity reference | Copied plain values | Entity ID, loaded in a delegate |
|---|---|---|---|
| Data freshness | Loads current state from the database when read, so changes made after start are visible | Frozen at the moment of copy until the process updates the variable | Loaded fresh at the point the delegate runs, so visible to later steps |
| Coupling to persistence model | High: the stored value depends on the entity class and its JPA mapping | Low: only the copied fields and their types matter | Medium: the ID type and the loader code depend on the entity model |
| Query support | Supports queries on JPA process variables, subject to the Flowable JPA docs | Standard variable queries on the copied values | Queries work on the ID only, so entity fields need a separate lookup |
| History size and audit | Stores a reference, but history shows what was read, so reviewers must confirm what the entity held at that time | Largest history if many fields are copied, but the snapshot is explicit and easy to read | Small history entries; the audit trail does not show the entity state unless you record it |
| Behavior when the entity changes or is deleted | Load may fail or return changed data, so the process needs a defined path | Process keeps the old values and may act on stale data | Delegate must handle a missing record and decide whether to fail or continue |
| Best fit | Short-lived processes where current data matters and the team controls the entity model | Long-running or audited processes where the decision must reflect the data at start | Processes that need current data at a few known steps and a simple, stable state |
Setting up JPA entity variables in a Spring-based Flowable application
This sequence takes a Spring team from a bare Flowable starter to a verified setup where process variables hold entity references that load and query correctly, and roll back with the surrounding transaction.
Add the Flowable Spring Boot starter
The developer adds the Flowable Spring Boot starter for the process engine to the build, and adds the REST or CMMN and DMN starters only if the application needs them. Confirm the starter versions match the Spring Boot generation in use, since Flowable 8.0.0 requires Spring Boot 4 and does not support Spring Boot 3. Check the Spring Boot docs for the starter names and the actuator endpoint at /actuator/flowable. The step is done when the application starts, the engine creates its tables against the chosen database, and the actuator endpoint responds.
Apply the JPA docs configuration
The developer applies the configuration that the Flowable JPA docs require for storing entity references in process variables, including the JPA entity and variable settings described there. Confirm the supported database type and any MySQL precision requirement from the configuration docs before choosing the schema. The check is a deployed test process that writes one JPA variable and reads it back by its entity identifier without errors in the logs.
Align the Flowable data source and transaction manager
The platform team points Flowable and the application's JPA layer at the same data source and transaction manager, or confirms that both are deliberately separate and documented. Shared transactions are what let an entity save and a process step commit or roll back together. Ask the team to confirm this with the Spring configuration, not from memory. The check is a logged transaction boundary: a service method that saves an entity and then signals a process should show one transaction covering both writes.
Define the entity and the variable access pattern
The developer defines the JPA entity, its identifier type, and a single access pattern in a Spring bean that reads the entity by ID when a delegate or expression needs it. Keep the process variable to the identifier and avoid copying entity fields into variables, so the database stays the source of truth. Decide which variables stay plain values, such as a status string or a decision outcome. The check is a code review against this rule, plus a delegate that loads the entity inside the same transaction.
Verify entity loading in an integration test
The team writes an integration test that starts the Spring context, creates a process instance with the entity reference, and confirms that a delegate loads the current entity state. Include a case where the entity changed after the variable was set, to confirm the process reads the latest row. The check is a passing test that asserts the loaded field values, not the stored copy, and a test log that shows the expected query.
Verify querying processes by entity
The developer queries running process instances by the JPA variable, following the query pattern in the JPA docs, and tests both a match and a non-match. Confirm the query returns the expected instance count and that the query uses the identifier the team stored. The check is a test asserting the returned instance list and an indexed query plan reviewed on the target database, since the docs do not specify performance for large volumes.
Verify rollback behavior
The team forces a failure after the entity save and after the process signal in separate tests, then confirms that the entity change and the process state roll back together under the shared transaction manager. Also confirm that a failure in the delegate leaves the process at the previous wait state. The check is a test that asserts both the database row and the active activity are unchanged after the forced error, and a documented decision for any case where the transactions are separate.
How Spring transactions change what a process sees
When a service task runs inside the same Spring-managed transaction as the process step that started it, the entity the task loads is normally the same persistence context instance the surrounding code already holds. Changes the task makes to that entity are visible to later steps in that transaction and are written together with the engine's own state when the transaction commits. This keeps the process and the business data consistent, but it also means the task's behavior depends on how the Spring transaction is configured, not only on the BPMN model.
- Shared persistence context: Confirm that the engine's transaction manager and your JPA transaction manager are the same bean, so both commit in one unit of work.
- Flush timing: JPA may defer SQL until flush or commit, so a query issued mid-step may not reflect in-memory changes unless a flush happens first.
- Lazy loading: An entity loaded in the task may need an open session to read associations, so decide whether the task fetches what it needs up front.
- Delegate bean scope: Delegates that hold entity fields across calls can leak state between executions, so keep entities as local values.
- Nested calls: A called sub-process or call activity that starts a new transaction changes the boundary, so confirm which steps share it.
Before relying on this behavior, test a task that updates an entity and then reads it back in a later step, and confirm the result with your Spring and Flowable versions.
A process that stops at a user task, timer or message event keeps only the entity reference in its variables while it waits. When the process resumes hours or days later, the engine has no live persistence context from the original request, so the entity is loaded again from the database. The version the process sees is whatever the database holds at resume time, which may include changes made by other users, batch jobs or other processes since the wait began.
- Stale decisions: A gateway that branched on an entity status at the start may now be running on a status that has changed since.
- Deleted records: An entity removed while the process waited will fail to load, so the step must handle a missing reference.
- Changed identifiers: Confirm that the stored reference uses a stable key and that the entity can be found by it after a restart.
- Re-check on resume: Decide where the process reads the current state again and where it relies on a value captured at task creation.
- Audit of the read: Record the values used for a decision in the process history if an auditor may ask what the process knew at the time.
Ask your team which values must be fixed at the moment of decision and which must be read fresh, and encode that split in the model.
When a step fails with an exception that propagates out of the transaction, the rollback normally covers both the engine's state change and any entity changes made in the same transaction. The process step is not recorded as complete, and the entity returns to its prior database state. This is the behavior most teams want for a single unit of work, but it has consequences for side effects that were not part of the database transaction, such as emails, payments or messages sent to external systems.
- Atomic step: Entity updates and the process advance commit or roll back together when they share a transaction.
- External side effects: A call to a payment or mail service inside the step is not rolled back, so it needs its own idempotency key or a compensating action.
- Caught exceptions: If a delegate catches the error and returns normally, the transaction commits the partial work, so catch only where the partial state is intended.
- Retries: Failed asynchronous jobs are retried by the engine, so each retry reloads the entity and reruns the step.
- Boundary placement: Where the async boundary sits in the model decides whether a failure rolls back to the last wait state or to the start of the transaction.
Test the failure path deliberately, including a forced exception after an entity change, and confirm the database and process state afterward.
An entity read at the start of a step and the same entity written at the end of it can differ, because other transactions may update the row in between. Without concurrency control, the later write can overwrite a change made by someone else. JPA offers optimistic locking through a version column, which makes the conflicting write fail instead of silently replacing data. The isolation level of the database also shapes what a read can see, so the answer depends on both the JPA mapping and the database configuration.
- Version columns: Confirm that entities used in processes carry an optimistic lock version and that the team handles the resulting conflict exception.
- Isolation level: Check the default isolation level of the database and of the Spring transaction, and document any override.
- Long steps: A step that calls out to slow services widens the window in which another change can land.
- Parallel branches: Two parallel paths that update the same entity can conflict, so model shared data writes deliberately.
- Snapshot in variables: Copying a few key values into process variables records what the process saw, but those copies can also go stale.
Ask your Spring and database administrators to confirm the isolation level and locking strategy in your environment before go-live.
The behavior described on the other tabs depends on settings in your own deployment, and the default is not always the one a reader expects. Confirm these points with your engineers and your database and Spring administrators, and check them against the Flowable version you run, since configuration keys and defaults can change between major releases. The Flowable JPA documentation is the starting point for how entities are referenced and queried.
- Transaction managers: Whether the engine and application use one shared transaction manager or separate ones, and where the boundaries are.
- Async boundaries: Which tasks run in asynchronous jobs and how those jobs open and commit transactions.
- Isolation and locking: The database isolation level, the optimistic lock mapping on each entity, and the conflict handling in code.
- External effects: Which steps call systems outside the database and how each handles repeated execution.
- Test evidence: Automated tests covering a wait state reload, a forced rollback and a concurrent update, with results kept for review.
Treat any mismatch between these answers and your design as a finding to resolve before the process goes into production.
Security and data exposure risks of entity variables
Storing entity references in process variables moves data access decisions out of the database and into the workflow engine. The cards below cover where that shift creates exposure, and each one names what the application must design because the engine will not do it for you.
Over-exposed entity fields
A process variable holding a JPA entity is readable by anyone who can query variables or view history, and the entity may carry fields that no task form should show, such as salary, national ID or internal risk scores. Because the engine resolves the whole entity, the fields visible to a process reader are whatever the entity exposes. Check which entity fields are reachable through variable APIs, REST responses and history queries, and restrict the entity or its serialization to what each consumer needs.
Stale or deleted records on resume
The engine stores only a reference, so when a waiting process resumes days later, the row it points to may have changed, been archived or been deleted. The process then acts on current data it did not see when it started. Decide whether each step must re-read and re-validate the entity, what happens when the record is missing, and whether the approval decision should capture the values the approver actually saw.
Access checks the engine does not perform
Flowable does not check whether the user completing a task is allowed to read or change the entity behind it. Anyone able to call the task API with a valid reference can act on that record unless your application checks ownership, tenant and role on every delegate, listener and service call. Confirm where those checks run, and test them with users who should be refused, not only users who should succeed.
PII retained in history tables
Variables that hold entity references are copied into runtime and history data when a process is recorded, so personal data can persist in history tables after the source record is purged or anonymized. Retention then depends on the history level you choose and on the cleanup schedule. Agree a retention period with your data protection owner, confirm which history tables store variable values, and define how erasure requests reach them.
Transaction and deletion gaps
Entity changes made in one transaction and process state saved in another can disagree after a failure, leaving a record updated while the process shows it was not, or the reverse. Deletion has the same problem when a process still references a row. Confirm that Spring transaction boundaries cover both the entity write and the engine call, and that your deletion path checks for active instances before removing data.
Limits to confirm before relying on entity variables
Storing JPA entity references in process variables couples process state to your persistence layer, Spring wiring and class definitions. The cards below cover the limits that vary by Flowable version, JPA provider and deployment, so each should be checked against the Flowable docs and your own tests before the pattern goes into production.
Flowable version and Spring Boot baseline
Entity variable behavior depends on the Flowable release and the Spring stack it runs on. Flowable 8.0.0 moved to Spring Framework 7 and Spring Boot 4 and dropped Spring Boot 3 compatibility, while Flowable 7 targeted Spring Boot 3 and Jakarta EE. Confirm which Spring Boot line your application uses and whether the JPA integration documented for that Flowable version matches your setup. Test the exact versions you plan to run.
Entity references across upgrades
A stored reference points to a table row and an entity class. When you upgrade Flowable, Spring or your persistence library, confirm that existing instances still resolve their references and that loading behaves the same. Add an upgrade test that resumes instances created on the previous version, because in-flight processes are the ones most likely to break.
Query support for your JPA provider
Querying processes by a JPA entity value depends on how the engine maps those variables to the database and on what your JPA provider and database support. Confirm the query syntax against the docs for your provider, and run each query you need against a realistic data volume. Do not assume that a query working in H2 in a unit test will behave the same way on your production database.
Serialization and class change risk
Entity classes evolve. Renaming a field, changing a relationship, moving a class to another package or changing its identifier type can leave stored references unreadable or pointing at the wrong data. Decide which entity changes are allowed while instances are running, document the rule for developers, and test resume paths after each entity change. Plain variables with explicit keys avoid much of this exposure.
Load cost on every resume
Each time a waiting process resumes, the engine can load the referenced entities to continue. Long-running processes that pass through many tasks, or that hold large object graphs, add database reads at every step. Measure the number of loads per instance, check whether lazy relationships are triggered, and confirm the behavior under the concurrency your async executor and job processing will produce.
Transaction and data freshness
A reference loads the current row, not the value that existed when the process reached the task. Confirm how Spring transaction boundaries wrap engine commands and entity loads in your design, and decide whether a process should see changes made by other users after it started. Write tests that change the entity between task steps and check the outcome your business rules require.
Questions teams ask before storing JPA entities in Flowable
What does this work cost, and what drives the effort up?
Cost follows the written scope, and DeployOpen does not publish a price list, so the quote is the figure to compare. The main drivers are how many process and case models carry entity references, how many JPA entities each model touches, how complex the transaction boundaries are, and how much of the existing Spring Data and JPA layer must change. Ask the quote to separate the one-time design and build work from any recurring support or retainer, and to list which test environments and data sets are included. Expect the effort to rise when entities are shared across services or when lazy-loaded relationships must be read inside a process step.
How long does a proof of concept take with a team that already knows Spring and JPA?
Duration depends mostly on how far the team already is with Spring transactions, JPA mappings and Flowable's Spring integration, so no fixed timeline applies. A team that already runs Spring Boot services with a working persistence layer can usually limit the proof of concept to one representative process that loads one entity, changes it and queries instances by that entity. A team new to either technology should add time for learning the transaction model and for agreeing the data model. Ask for the proof of concept scope in writing, including which entity, which process and which failure cases it will exercise.
What must our team provide before the work starts?
Three inputs decide whether the design holds up: a data model, a transaction design and representative test data. The data model should identify which JPA entities processes will reference, their identifiers and which fields must be current when a step runs. The transaction design should state where Spring transactions start and end relative to Flowable commands, and how a rolled-back step is handled. Test data should include entities in the states the process will meet, such as deleted or changed mid-process records. Confirm with your own architects who owns each entity and who can approve schema changes.
Can an internal team build this without an outside partner?
Yes, if the team has engineers who can work across Spring, JPA and the Flowable Java API and who can test process behavior. The engine is open source under Apache 2.0, and the Flowable JPA docs describe the reference pattern, so the knowledge is available to any team. The risks are less about the first build and more about keeping the pattern consistent as models change and as entities evolve. Decide who will own the reference pattern, the test suite and the upgrade path before deciding between an internal build and a partner-led one, and check that the in-house team has time for review.
What breaks when a referenced entity is deleted or changed while a process is running?
A stored reference only points to a row, so a deleted or changed entity surfaces later, when a step loads it. Engineers should design explicit handling for a missing entity, such as a failed job, a retry or a human task, and should decide whether the process reads the latest state or a snapshot. Test these cases before go-live, because the failure often appears hours or days after the change. Check the async executor behavior in the Flowable advanced docs for retries and failed jobs, and confirm how your team will monitor them.
Does storing entity references create a security or data protection concern?
It can, because a process variable that holds a reference may expose data to anyone who can read process instances, history or queries. Storing only an identifier limits what sits in the process tables, but the loaded entity still carries whatever the application authorizes at load time. The engine does not decide who may see an entity, so your application must add those checks. Confirm with your security and privacy teams which entities are personal data, how long history retains references, and where audit events are kept.
When are plain variables the safer choice?
Plain variables are safer when the value is small, stable and needed by the process as a record of what was decided, such as an amount, a status or a decision outcome. They are also safer when the process must show the exact data it used at the time, because a reference reloads current state and may differ from what was shown to an approver. Use entity references when the process needs live data, when many steps read the same record, or when queries must filter instances by entity. Document the rule in your design so that teams add new variables consistently.
Will this approach complicate a later upgrade or migration?
It can, because entity references tie process data to your application schema and to the Spring and JPA versions in use. Flowable's major releases change the platform baseline, so a team that upgrades Spring or Flowable should test that stored references still load and that JPA queries still return the expected instances. Plan the upgrade test set before the upgrade, and keep the entity mapping under version control. Confirm the baseline requirements for your target release in the Flowable 8.0.0 release notes before planning the work.
Where to read the Flowable JPA and Spring Boot documentation
Start with the Flowable JPA chapter before you write any entity-backed variable, because it defines the storage model you are committing to: the engine stores a reference to the entity and loads it when a task or expression needs it, rather than serializing a copy into the process variable table. The chapter also covers querying process instances by JPA variable values, and includes an example that combines Spring beans with JPA. Read it with your own domain model open, and confirm which entity types, identifier types and query patterns the documented example covers before you assume your design matches it. The chapter is the Flowable JPA documentation, and it is short enough to read in one sitting.
The Spring Boot chapter answers the questions the JPA chapter leaves open: how the Flowable starters wire the engine to your Spring data source, transaction manager and entity manager. Check that the engine and your application share one transaction boundary and one persistence context in your version, and confirm the starter version against the Spring Boot baseline your team already runs. Confirm the exact starter coordinates and supported Spring Boot line in the chapter for the Flowable release you plan to deploy, since the supported Spring Boot versions differ between Flowable major releases. The actuator endpoint described there is useful later for checking that entity-backed instances are running as expected.
The Advanced chapter explains the async executor, which matters here because a job that loads an entity from a variable runs on a worker thread, outside the request that started the process. Read how the executor picks up jobs and recovers expired locks, then test a job that loads an entity after the original transaction has committed and the entity has changed or been deleted. Plan for that failure case in your delegates before go-live, and confirm the executor settings for your cluster in that chapter.
How DeployOpen scopes and delivers Flowable entity variable designs
DeployOpen plans, builds, documents and supports Flowable engines inside your own infrastructure, including the decision on which JPA entities become process variables and which data stays as plain values. Engagements are fixed-scope, so the entity mapping, query rules and transaction design are agreed in writing before any build work starts.
How we scope
- Inventory every JPA entity your processes would reference and the business reason each one must be carried as a variable.
- Record which entity fields the process reads, which it writes, and which must never be held in process state.
- Agree in writing how entity references are loaded, queried and refreshed, and where plain variables will be used instead.
- Confirm your database, Spring version and Flowable version, since the scope depends on the versions your environment runs.
How we build
- Implement entity references in delegates and Spring services so each task loads current data from the owning transaction.
- Build the process queries your operations teams need against JPA process variables, with tests for each query path.
- Align Flowable transactions with Spring transaction boundaries so a rollback never leaves a half-updated entity or stale reference.
- Keep plain variable fallbacks for values that must be frozen at a point in time, such as an approved amount or a decision input.
How we hand over
- Deliver the entity variable design document, listing each variable, its entity type, its owner and its lifecycle.
- Provide automated tests covering entity loading, missing or deleted entities, and transaction rollback scenarios.
- Document the query patterns, indexing expectations and history implications so your team can extend the design safely.
- Run a handover session with your developers and operators, and record open decisions for your own team to own.
How we support
- Review entity variable changes whenever your data model or Flowable version changes, before those changes reach production.
- Offer ongoing support, upgrades and continuous testing as a retainer, scoped in writing to your environment.
- Confirm with your team how entity schema changes will be coordinated with running process instances during each release.
- Ask DeployOpen to quote retainer coverage separately from the initial fixed-scope design work.

