From 9b4d6333ee006c5200629f356fe2bfdec78f7ca8 Mon Sep 17 00:00:00 2001
From: BertMatthys <84137009+BertMatthys@users.noreply.github.com>
Date: Mon, 24 Aug 2026 15:40:29 +0200
Subject: [PATCH] Add inheritVariables property to CMMN CaseTask and
ProcessTask
---
.../cmmn/converter/CaseTaskXmlConverter.java | 5 ++++
.../cmmn/converter/CmmnXmlConstants.java | 1 +
.../converter/ProcessTaskXmlConverter.java | 5 ++++
.../export/AbstractChildTaskExport.java | 3 +++
.../CaseTaskCmmnXmlConverterTest.java | 1 +
.../ProcessTask2CmmnXmlConverterTest.java | 1 +
.../test/cmmn/converter/case-task.cmmn | 2 +-
.../test/cmmn/converter/process-task2.cmmn | 2 +-
.../flowable/cmmn/test/ProcessTaskTest.java | 27 +++++++++++++++++++
...kTest.testProcessTaskInheritVariables.cmmn | 19 +++++++++++++
.../impl/CaseTaskActivityBehavior.java | 3 ++-
.../impl/ChildTaskActivityBehavior.java | 13 +++++++++
.../impl/ProcessTaskActivityBehavior.java | 3 ++-
.../cmmn/test/runtime/CaseTaskTest.java | 26 ++++++++++++++++++
...TaskTest.testCaseTaskInheritVariables.cmmn | 16 +++++++++++
.../org/flowable/cmmn/model/ChildTask.java | 9 +++++++
16 files changed, 132 insertions(+), 4 deletions(-)
create mode 100644 modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/ProcessTaskTest.testProcessTaskInheritVariables.cmmn
create mode 100644 modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/CaseTaskTest.testCaseTaskInheritVariables.cmmn
diff --git a/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/CaseTaskXmlConverter.java b/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/CaseTaskXmlConverter.java
index 4a0bd67fd22..6edd7c848e5 100644
--- a/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/CaseTaskXmlConverter.java
+++ b/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/CaseTaskXmlConverter.java
@@ -44,6 +44,11 @@ protected CmmnElement convert(XMLStreamReader xtr, ConversionHelper conversionHe
caseTask.setInheritBusinessKey(Boolean.parseBoolean(inheritBusinessKey));
}
+ String inheritVariables = xtr.getAttributeValue(CmmnXmlConstants.FLOWABLE_EXTENSIONS_NAMESPACE, CmmnXmlConstants.ATTRIBUTE_INHERIT_VARIABLES);
+ if (inheritVariables != null) {
+ caseTask.setInheritVariables(Boolean.parseBoolean(inheritVariables));
+ }
+
String fallbackToDefaultTenantValue = xtr.getAttributeValue(CmmnXmlConstants.FLOWABLE_EXTENSIONS_NAMESPACE, CmmnXmlConstants.ATTRIBUTE_FALLBACK_TO_DEFAULT_TENANT);
if (fallbackToDefaultTenantValue != null) {
caseTask.setFallbackToDefaultTenant(Boolean.valueOf(fallbackToDefaultTenantValue));
diff --git a/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/CmmnXmlConstants.java b/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/CmmnXmlConstants.java
index fce5fd993ce..f1bf6797d1c 100644
--- a/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/CmmnXmlConstants.java
+++ b/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/CmmnXmlConstants.java
@@ -142,6 +142,7 @@ public interface CmmnXmlConstants {
String ATTRIBUTE_FALLBACK_TO_DEFAULT_TENANT = "fallbackToDefaultTenant";
String ATTRIBUTE_BUSINESS_KEY = "businessKey";
String ATTRIBUTE_INHERIT_BUSINESS_KEY = "inheritBusinessKey";
+ String ATTRIBUTE_INHERIT_VARIABLES = "inheritVariables";
String ATTRIBUTE_ID_VARIABLE_NAME = "idVariableName";
String ELEMENT_CASE_REF_EXPRESSION = "caseRefExpression";
diff --git a/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/ProcessTaskXmlConverter.java b/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/ProcessTaskXmlConverter.java
index c614f5fdc3f..67aab2d3f09 100644
--- a/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/ProcessTaskXmlConverter.java
+++ b/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/ProcessTaskXmlConverter.java
@@ -44,6 +44,11 @@ protected CmmnElement convert(XMLStreamReader xtr, ConversionHelper conversionHe
processTask.setInheritBusinessKey(Boolean.parseBoolean(inheritBusinessKey));
}
+ String inheritVariables = xtr.getAttributeValue(CmmnXmlConstants.FLOWABLE_EXTENSIONS_NAMESPACE, CmmnXmlConstants.ATTRIBUTE_INHERIT_VARIABLES);
+ if (inheritVariables != null) {
+ processTask.setInheritVariables(Boolean.parseBoolean(inheritVariables));
+ }
+
String fallbackToDefaultTenantValue = xtr.getAttributeValue(CmmnXmlConstants.FLOWABLE_EXTENSIONS_NAMESPACE, CmmnXmlConstants.ATTRIBUTE_FALLBACK_TO_DEFAULT_TENANT);
if (fallbackToDefaultTenantValue != null) {
processTask.setFallbackToDefaultTenant(Boolean.parseBoolean(fallbackToDefaultTenantValue));
diff --git a/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/export/AbstractChildTaskExport.java b/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/export/AbstractChildTaskExport.java
index d90c74482c3..52a28773b6c 100644
--- a/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/export/AbstractChildTaskExport.java
+++ b/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/export/AbstractChildTaskExport.java
@@ -32,6 +32,9 @@ protected void writePlanItemDefinitionSpecificAttributes(T planItemDefinition, X
if (planItemDefinition.isInheritBusinessKey()) {
xtw.writeAttribute(FLOWABLE_EXTENSIONS_PREFIX, FLOWABLE_EXTENSIONS_NAMESPACE, ATTRIBUTE_INHERIT_BUSINESS_KEY, String.valueOf(planItemDefinition.isInheritBusinessKey()));
}
+ if (planItemDefinition.isInheritVariables()) {
+ xtw.writeAttribute(FLOWABLE_EXTENSIONS_PREFIX, FLOWABLE_EXTENSIONS_NAMESPACE, ATTRIBUTE_INHERIT_VARIABLES, String.valueOf(planItemDefinition.isInheritVariables()));
+ }
}
@Override
diff --git a/modules/flowable-cmmn-converter/src/test/java/org/flowable/test/cmmn/converter/CaseTaskCmmnXmlConverterTest.java b/modules/flowable-cmmn-converter/src/test/java/org/flowable/test/cmmn/converter/CaseTaskCmmnXmlConverterTest.java
index ba068d43693..5673f927e53 100644
--- a/modules/flowable-cmmn-converter/src/test/java/org/flowable/test/cmmn/converter/CaseTaskCmmnXmlConverterTest.java
+++ b/modules/flowable-cmmn-converter/src/test/java/org/flowable/test/cmmn/converter/CaseTaskCmmnXmlConverterTest.java
@@ -52,6 +52,7 @@ public void validateModel(CmmnModel cmmnModel) {
assertThat(task1.getCaseRefExpression()).isEqualTo("caseDefinitionKey");
assertThat(task1.getFallbackToDefaultTenant()).isTrue();
assertThat(task1.isSameDeployment()).isTrue();
+ assertThat(task1.isInheritVariables()).isTrue();
assertThat(task1.getInParameters())
.extracting(IOParameter::getSource, IOParameter::getTarget)
diff --git a/modules/flowable-cmmn-converter/src/test/java/org/flowable/test/cmmn/converter/ProcessTask2CmmnXmlConverterTest.java b/modules/flowable-cmmn-converter/src/test/java/org/flowable/test/cmmn/converter/ProcessTask2CmmnXmlConverterTest.java
index f17798afaa8..3d9085494e5 100644
--- a/modules/flowable-cmmn-converter/src/test/java/org/flowable/test/cmmn/converter/ProcessTask2CmmnXmlConverterTest.java
+++ b/modules/flowable-cmmn-converter/src/test/java/org/flowable/test/cmmn/converter/ProcessTask2CmmnXmlConverterTest.java
@@ -49,6 +49,7 @@ public void validateModel(CmmnModel cmmnModel) {
.isInstanceOfSatisfying(ProcessTask.class, task1 -> {
assertThat(task1.getProcessRefExpression()).isEqualTo("myTestProcess");
assertThat((task1.isSameDeployment())).isTrue();
+ assertThat(task1.isInheritVariables()).isTrue();
assertThat(task1.getInParameters()).isEmpty();
assertThat(task1.getOutParameters()).isEmpty();
diff --git a/modules/flowable-cmmn-converter/src/test/resources/org/flowable/test/cmmn/converter/case-task.cmmn b/modules/flowable-cmmn-converter/src/test/resources/org/flowable/test/cmmn/converter/case-task.cmmn
index c3bc7d5baff..39e9965347f 100644
--- a/modules/flowable-cmmn-converter/src/test/resources/org/flowable/test/cmmn/converter/case-task.cmmn
+++ b/modules/flowable-cmmn-converter/src/test/resources/org/flowable/test/cmmn/converter/case-task.cmmn
@@ -21,7 +21,7 @@
-
+
diff --git a/modules/flowable-cmmn-converter/src/test/resources/org/flowable/test/cmmn/converter/process-task2.cmmn b/modules/flowable-cmmn-converter/src/test/resources/org/flowable/test/cmmn/converter/process-task2.cmmn
index 2a4754d68f4..31dcabbacef 100644
--- a/modules/flowable-cmmn-converter/src/test/resources/org/flowable/test/cmmn/converter/process-task2.cmmn
+++ b/modules/flowable-cmmn-converter/src/test/resources/org/flowable/test/cmmn/converter/process-task2.cmmn
@@ -17,7 +17,7 @@
-
+
diff --git a/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/ProcessTaskTest.java b/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/ProcessTaskTest.java
index aa6f07e8267..d7edbbc7869 100644
--- a/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/ProcessTaskTest.java
+++ b/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/ProcessTaskTest.java
@@ -1015,6 +1015,33 @@ public void testIOParameterCombinations() {
.hasSize(4 + 4); // 4 from in mapping, 4 from task complete
}
+ @Test
+ @CmmnDeployment
+ @org.flowable.engine.test.Deployment(resources = "org/flowable/cmmn/test/oneTaskProcess.bpmn20.xml")
+ public void testProcessTaskInheritVariables() {
+ CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder()
+ .caseDefinitionKey("myCase")
+ .variable("caseVariableA", "hello")
+ .variable("caseVariableB", "world")
+ .variable("caseVariableC", 42)
+ .start();
+
+ PlanItemInstance processTaskPlanItemInstance = cmmnRuntimeService.createPlanItemInstanceQuery()
+ .caseInstanceId(caseInstance.getId())
+ .planItemDefinitionType(PlanItemDefinitionType.PROCESS_TASK)
+ .singleResult();
+ String processInstanceId = processTaskPlanItemInstance.getReferenceId();
+ ProcessInstance processInstance = processEngineRuntimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
+ assertThat(processInstance).isNotNull();
+
+ // All case variables are inherited into the child process instance (like a BPMN call activity)
+ assertThat(processEngineRuntimeService.getVariable(processInstanceId, "caseVariableA")).isEqualTo("hello");
+ assertThat(processEngineRuntimeService.getVariable(processInstanceId, "caseVariableC")).isEqualTo(42);
+
+ // An explicit in parameter takes precedence over the inherited variable
+ assertThat(processEngineRuntimeService.getVariable(processInstanceId, "caseVariableB")).isEqualTo("overridden");
+ }
+
@Test
@CmmnDeployment
public void testProcessTaskWithSkipExpressions() {
diff --git a/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/ProcessTaskTest.testProcessTaskInheritVariables.cmmn b/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/ProcessTaskTest.testProcessTaskInheritVariables.cmmn
new file mode 100644
index 00000000000..7062df23cc3
--- /dev/null
+++ b/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/ProcessTaskTest.testProcessTaskInheritVariables.cmmn
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/CaseTaskActivityBehavior.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/CaseTaskActivityBehavior.java
index f62dbd9eb36..0d1bcfeb442 100644
--- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/CaseTaskActivityBehavior.java
+++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/CaseTaskActivityBehavior.java
@@ -57,7 +57,8 @@ public class CaseTaskActivityBehavior extends ChildTaskActivityBehavior implemen
protected CaseTask caseTask;
public CaseTaskActivityBehavior(Expression caseRefExpression, CaseTask caseTask) {
- super(caseTask.isBlocking(), caseTask.getBlockingExpression(), caseTask.getInParameters(), caseTask.getOutParameters());
+ super(caseTask.isBlocking(), caseTask.getBlockingExpression(), caseTask.getInParameters(), caseTask.getOutParameters(),
+ caseTask.isInheritVariables());
this.caseRefExpression = caseRefExpression;
this.caseRef = caseTask.getCaseRef();
this.fallbackToDefaultTenant = caseTask.getFallbackToDefaultTenant();
diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/ChildTaskActivityBehavior.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/ChildTaskActivityBehavior.java
index 866bc0edd00..f9d12caf70e 100644
--- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/ChildTaskActivityBehavior.java
+++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/ChildTaskActivityBehavior.java
@@ -43,6 +43,7 @@ public abstract class ChildTaskActivityBehavior extends CoreCmmnTriggerableActiv
protected String isBlockingExpression;
protected List inParameters;
protected List outParameters;
+ protected boolean inheritVariables;
public ChildTaskActivityBehavior(boolean isBlocking, String isBlockingExpression) {
this.isBlocking = isBlocking;
@@ -50,9 +51,15 @@ public ChildTaskActivityBehavior(boolean isBlocking, String isBlockingExpression
}
public ChildTaskActivityBehavior(boolean isBlocking, String isBlockingExpression, List inParameters, List outParameters) {
+ this(isBlocking, isBlockingExpression, inParameters, outParameters, false);
+ }
+
+ public ChildTaskActivityBehavior(boolean isBlocking, String isBlockingExpression, List inParameters, List outParameters,
+ boolean inheritVariables) {
this(isBlocking, isBlockingExpression);
this.inParameters = inParameters;
this.outParameters = outParameters;
+ this.inheritVariables = inheritVariables;
}
@Override
@@ -83,6 +90,12 @@ protected void handleInParameters(PlanItemInstanceEntity planItemInstanceEntity,
CmmnEngineConfiguration cmmnEngineConfiguration, Map inParametersMap,
ExpressionManager expressionManager) {
+ if (inheritVariables) {
+ // Inherit all variables from the parent. Explicit in parameters are
+ // applied afterwards, so they take precedence over the inherited variables.
+ inParametersMap.putAll(planItemInstanceEntity.getVariables());
+ }
+
IOParameterUtil.processInParameters(inParameters, planItemInstanceEntity, inParametersMap, expressionManager);
}
diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/ProcessTaskActivityBehavior.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/ProcessTaskActivityBehavior.java
index c47331e67fa..7119c012ede 100644
--- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/ProcessTaskActivityBehavior.java
+++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/ProcessTaskActivityBehavior.java
@@ -57,7 +57,8 @@ public class ProcessTaskActivityBehavior extends ChildTaskActivityBehavior imple
protected ProcessTask processTask;
public ProcessTaskActivityBehavior(Process process, Expression processRefExpression, ProcessTask processTask) {
- super(processTask.isBlocking(), processTask.getBlockingExpression(), processTask.getInParameters(), processTask.getOutParameters());
+ super(processTask.isBlocking(), processTask.getBlockingExpression(), processTask.getInParameters(), processTask.getOutParameters(),
+ processTask.isInheritVariables());
this.process = process;
this.processRefExpression = processRefExpression;
this.processRef = processTask.getProcessRef();
diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/CaseTaskTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/CaseTaskTest.java
index 8ff291a8626..bd24368a082 100644
--- a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/CaseTaskTest.java
+++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/CaseTaskTest.java
@@ -1236,6 +1236,32 @@ public void testWithInheritBusinessKeyButWithoutBusinessKey() {
cmmnRuntimeService.triggerPlanItemInstance(planItemInstance.getId());
}
+ @Test
+ @CmmnDeployment(resources = {
+ "org/flowable/cmmn/test/runtime/CaseTaskTest.testCaseTaskInheritVariables.cmmn",
+ "org/flowable/cmmn/test/runtime/oneTaskCase.cmmn"
+ })
+ public void testCaseTaskInheritVariables() {
+ CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder()
+ .caseDefinitionKey("myCase")
+ .variable("caseVariableA", "hello")
+ .variable("caseVariableB", "world")
+ .variable("caseVariableC", 42)
+ .start();
+
+ CaseInstance subCase = cmmnRuntimeService.createCaseInstanceQuery()
+ .caseDefinitionKey("oneTaskCase")
+ .singleResult();
+ assertThat(subCase).isNotNull();
+
+ // All parent case variables are inherited into the child case instance (like a BPMN call activity)
+ assertThat(cmmnRuntimeService.getVariable(subCase.getId(), "caseVariableA")).isEqualTo("hello");
+ assertThat(cmmnRuntimeService.getVariable(subCase.getId(), "caseVariableC")).isEqualTo(42);
+
+ // An explicit in parameter takes precedence over the inherited variable
+ assertThat(cmmnRuntimeService.getVariable(subCase.getId(), "caseVariableB")).isEqualTo("overridden");
+ }
+
@Test
@CmmnDeployment
public void testIdVariableName() {
diff --git a/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/CaseTaskTest.testCaseTaskInheritVariables.cmmn b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/CaseTaskTest.testCaseTaskInheritVariables.cmmn
new file mode 100644
index 00000000000..adaeedb0ffd
--- /dev/null
+++ b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/CaseTaskTest.testCaseTaskInheritVariables.cmmn
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/flowable-cmmn-model/src/main/java/org/flowable/cmmn/model/ChildTask.java b/modules/flowable-cmmn-model/src/main/java/org/flowable/cmmn/model/ChildTask.java
index 59f705488ba..e7017b1c0d5 100644
--- a/modules/flowable-cmmn-model/src/main/java/org/flowable/cmmn/model/ChildTask.java
+++ b/modules/flowable-cmmn-model/src/main/java/org/flowable/cmmn/model/ChildTask.java
@@ -22,6 +22,7 @@ public class ChildTask extends Task implements HasInParameters, HasOutParameters
protected String businessKey;
protected boolean inheritBusinessKey;
+ protected boolean inheritVariables;
protected List inParameters = new ArrayList<>();
protected List outParameters = new ArrayList<>();
@@ -41,6 +42,14 @@ public void setInheritBusinessKey(boolean inheritBusinessKey) {
this.inheritBusinessKey = inheritBusinessKey;
}
+ public boolean isInheritVariables() {
+ return inheritVariables;
+ }
+
+ public void setInheritVariables(boolean inheritVariables) {
+ this.inheritVariables = inheritVariables;
+ }
+
@Override
public List getInParameters() {
return inParameters;