This is an automated email from the ASF dual-hosted git repository.

martinweiler pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie-drools.git


The following commit(s) were added to refs/heads/main by this push:
     new 7616139f126 [incubator-kie-issue-2381] Support dynamic process calling 
(#6824)
7616139f126 is described below

commit 7616139f1263a18b81537163a4da8a8ae8837dbb
Author: Christine-Jose <[email protected]>
AuthorDate: Fri Jul 24 20:20:48 2026 +0530

    [incubator-kie-issue-2381] Support dynamic process calling (#6824)
    
    * Changes for dynamic subprocess calling
    
    * Review comments
    
    * Fixing failed tests
    
    * reverting the NodeCreator.java
    
    * Adding the fix for java.lang.NoSuchMethodException;
---
 .../kogito/codegen/tests/CallActivityTaskIT.java   |  34 ++
 .../process/process-generation-test.skip.txt       |   2 +
 .../subprocess/CallActivityHyphenated.bpmn2        |  89 ++++
 .../CallActivityHyphenatedSubProcess.bpmn2         |  71 ++++
 .../kogito/codegen/process/ProcessGenerator.java   |   7 +-
 .../process/bpmn2/StaticApplicationAssembler.java  |  64 ---
 .../canonical/LambdaSubProcessNodeVisitor.java     | 203 +--------
 .../class-templates/SubProcessFactoryTemplate.java |  31 --
 .../core/factory/SubProcessNodeFactory.java        |   7 -
 .../jbpm/workflow/core/node/SubProcessFactory.java |  30 --
 .../jbpm/workflow/core/node/SubProcessNode.java    |  10 -
 .../workflow/instance/impl/NodeInstanceImpl.java   |  10 +
 .../node/LambdaSubProcessNodeInstance.java         |  95 ++++-
 .../java/org/jbpm/process/test/NodeCreator.java    |   4 +-
 .../BPMN2-DynamicCallActivityByExpression.bpmn2    |  74 ++++
 .../BPMN2-DynamicCallActivityByVariable.bpmn2      |  75 ++++
 .../BPMN2-DynamicCallActivityMissingVariable.bpmn2 |  49 +++
 .../BPMN2-DynamicCallActivityMultiExpression.bpmn2 |  77 ++++
 .../BPMN2-DynamicCallActivityNoWait.bpmn2          |  55 +++
 .../BPMN2-DynamicCallActivityScriptResolved.bpmn2  |  80 ++++
 .../BPMN2-DynamicCallActivityTwoSequential.bpmn2   |  72 ++++
 .../BPMN2-DynamicCallActivityUnknownProcess.bpmn2  |  51 +++
 ...PMN2-DynamicCallActivityWithBoundaryError.bpmn2 | 100 +++++
 .../subprocess/BPMN2-DynamicSubProcessA.bpmn2      |  49 +++
 .../subprocess/BPMN2-DynamicSubProcessB.bpmn2      |  49 +++
 .../subprocess/BPMN2-DynamicSubProcessFaulty.bpmn2 |  63 +++
 .../java/org/jbpm/bpmn2/DynamicSubProcessTest.java | 468 +++++++++++++++++++++
 27 files changed, 1552 insertions(+), 367 deletions(-)

diff --git 
a/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/tests/CallActivityTaskIT.java
 
b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/tests/CallActivityTaskIT.java
index 98faa042912..e06b5c9799f 100644
--- 
a/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/tests/CallActivityTaskIT.java
+++ 
b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/tests/CallActivityTaskIT.java
@@ -216,4 +216,38 @@ public class CallActivityTaskIT extends AbstractCodegenIT {
                 .containsEntry("x", "a");
     }
 
+    /**
+     * Verifies that a subprocess whose process id contains hyphens 
+     */
+    @Test
+    public void testCallActivityWithHyphenatedSubProcessId() throws Exception {
+
+        Application app = generateCodeProcessesOnly(
+                "subprocess/CallActivityHyphenated.bpmn2",
+                "subprocess/CallActivityHyphenatedSubProcess.bpmn2");
+        assertThat(app).isNotNull();
+
+        // The hyphenated subprocess must be reachable by its original id
+        
assertThat(app.get(Processes.class).processById("call-activity-sub-process")).isNotNull();
+
+        Process<? extends Model> p = 
app.get(Processes.class).processById("ParentProcessHyphenated");
+        assertThat(p).isNotNull();
+
+        Model m = p.createModel();
+        Map<String, Object> parameters = new HashMap<>();
+        parameters.put("x", "inputValue");
+        parameters.put("y", "");
+        m.fromMap(parameters);
+
+        ProcessInstance<?> processInstance = p.createInstance(m);
+        processInstance.start();
+
+        
assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_COMPLETED);
+        Model result = (Model) processInstance.variables();
+        // The child sets subY = "hyphenated result"; it is mapped back into 
parent's y
+        assertThat(result.toMap())
+                .containsEntry("y", "hyphenated result")
+                .containsEntry("x", "inputValue");
+    }
+
 }
diff --git 
a/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/org/kie/kogito/codegen/process/process-generation-test.skip.txt
 
b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/org/kie/kogito/codegen/process/process-generation-test.skip.txt
index b1cbbd61950..02249fcef4e 100644
--- 
a/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/org/kie/kogito/codegen/process/process-generation-test.skip.txt
+++ 
b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/org/kie/kogito/codegen/process/process-generation-test.skip.txt
@@ -22,3 +22,5 @@ links/DifferentLinkProcess.bpmn2
 links/EmptyLinkProcess.bpmn2
 links/MultipleCatchLinkProcess.bpmn2
 links/UnconnectedLinkProcess.bpmn2
+subprocess/CallActivityHyphenated.bpmn2
+subprocess/CallActivityHyphenatedSubProcess.bpmn2
diff --git 
a/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/subprocess/CallActivityHyphenated.bpmn2
 
b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/subprocess/CallActivityHyphenated.bpmn2
new file mode 100644
index 00000000000..796c14489e5
--- /dev/null
+++ 
b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/subprocess/CallActivityHyphenated.bpmn2
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<definitions id="Definition"
+             targetNamespace="http://www.example.org/HyphenatedSubprocess";
+             typeLanguage="http://www.java.com/javaTypes";
+             expressionLanguage="http://www.mvel.org/2.0";
+             xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL";
+             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+             xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL 
BPMN20.xsd"
+             xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI";
+             xmlns:dc="http://www.omg.org/spec/DD/20100524/DC";
+             xmlns:di="http://www.omg.org/spec/DD/20100524/DI";
+             xmlns:tns="http://www.jboss.org/drools";>
+
+  <itemDefinition id="_xItem" structureRef="String"/>
+  <itemDefinition id="_yItem" structureRef="String"/>
+
+  <!--
+    Parent process whose callActivity invokes a subprocess whose id contains 
hyphens.
+    ProcessGenerator must sanitize the subprocess id when generating the Java 
field
+    name (e.g. "call-activity-sub-process" -> 
"processCall_activity_sub_process").
+    Without the fix (using sanitizeClassName) this would produce an invalid 
Java
+    identifier and fail to compile.
+  -->
+  <process processType="Private" isExecutable="true"
+           id="ParentProcessHyphenated"
+           name="Parent Process Hyphenated">
+
+    <property id="x" itemSubjectRef="_xItem"/>
+    <property id="y" itemSubjectRef="_yItem"/>
+
+    <startEvent id="_1" name="StartProcess"/>
+    <callActivity id="_2" name="CallActivity" 
calledElement="call-activity-sub-process">
+      <ioSpecification>
+        <dataInput  id="_2_subXInput"  tns:dtype="String" name="subX"/>
+        <dataOutput id="_2_subYOutput" tns:dtype="String" name="subY"/>
+        <inputSet>
+          <dataInputRefs>_2_subXInput</dataInputRefs>
+        </inputSet>
+        <outputSet>
+          <dataOutputRefs>_2_subYOutput</dataOutputRefs>
+        </outputSet>
+      </ioSpecification>
+      <dataInputAssociation>
+        <sourceRef>x</sourceRef>
+        <targetRef>_2_subXInput</targetRef>
+      </dataInputAssociation>
+      <dataOutputAssociation>
+        <sourceRef>_2_subYOutput</sourceRef>
+        <targetRef>y</targetRef>
+      </dataOutputAssociation>
+    </callActivity>
+    <endEvent id="_3" name="EndProcess">
+      <terminateEventDefinition/>
+    </endEvent>
+
+    <sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2"/>
+    <sequenceFlow id="_2-_3" sourceRef="_2" targetRef="_3"/>
+  </process>
+
+  <bpmndi:BPMNDiagram>
+    <bpmndi:BPMNPlane bpmnElement="ParentProcessHyphenated">
+      <bpmndi:BPMNShape bpmnElement="_1"><dc:Bounds x="16"  y="16" width="48"  
height="48"/></bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="_2"><dc:Bounds x="96"  y="16" width="110" 
height="48"/></bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="_3"><dc:Bounds x="238" y="16" width="48"  
height="48"/></bpmndi:BPMNShape>
+      <bpmndi:BPMNEdge bpmnElement="_1-_2"><di:waypoint x="40"  
y="40"/><di:waypoint x="151" y="40"/></bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge bpmnElement="_2-_3"><di:waypoint x="151" 
y="40"/><di:waypoint x="262" y="40"/></bpmndi:BPMNEdge>
+    </bpmndi:BPMNPlane>
+  </bpmndi:BPMNDiagram>
+
+</definitions>
diff --git 
a/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/subprocess/CallActivityHyphenatedSubProcess.bpmn2
 
b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/subprocess/CallActivityHyphenatedSubProcess.bpmn2
new file mode 100644
index 00000000000..797b481ec9c
--- /dev/null
+++ 
b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/resources/subprocess/CallActivityHyphenatedSubProcess.bpmn2
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<definitions id="Definition"
+             targetNamespace="http://www.example.org/HyphenatedSubprocess";
+             typeLanguage="http://www.java.com/javaTypes";
+             expressionLanguage="http://www.mvel.org/2.0";
+             xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL";
+             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+             xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL 
BPMN20.xsd"
+             xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI";
+             xmlns:dc="http://www.omg.org/spec/DD/20100524/DC";
+             xmlns:di="http://www.omg.org/spec/DD/20100524/DI";
+             xmlns:tns="http://www.jboss.org/drools";>
+
+  <itemDefinition id="_subXItem" structureRef="String"/>
+  <itemDefinition id="_subYItem" structureRef="String"/>
+
+  <!--
+    Subprocess with a hyphenated process id: "call-activity-sub-process".
+    The hyphens must be handled by ProcessGenerator.sanitizeClassName() so the
+    generated Java field name is a valid identifier.
+  -->
+  <process processType="Private" isExecutable="true"
+           id="call-activity-sub-process"
+           name="Call Activity Sub Process"
+           tns:version="1">
+
+    <property id="subX" itemSubjectRef="_subXItem"/>
+    <property id="subY" itemSubjectRef="_subYItem"/>
+
+    <startEvent id="_1" name="StartProcess"/>
+    <scriptTask id="_2" name="SetOutput">
+      <script>kcontext.setVariable("subY", "hyphenated result");</script>
+    </scriptTask>
+    <endEvent id="_3" name="EndProcess">
+      <terminateEventDefinition/>
+    </endEvent>
+
+    <sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2"/>
+    <sequenceFlow id="_2-_3" sourceRef="_2" targetRef="_3"/>
+  </process>
+
+  <bpmndi:BPMNDiagram>
+    <bpmndi:BPMNPlane bpmnElement="call-activity-sub-process">
+      <bpmndi:BPMNShape bpmnElement="_1"><dc:Bounds x="16"  y="16" width="48" 
height="48"/></bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="_2"><dc:Bounds x="96"  y="16" width="80" 
height="48"/></bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="_3"><dc:Bounds x="208" y="16" width="48" 
height="48"/></bpmndi:BPMNShape>
+      <bpmndi:BPMNEdge bpmnElement="_1-_2"><di:waypoint x="40"  
y="40"/><di:waypoint x="136" y="40"/></bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge bpmnElement="_2-_3"><di:waypoint x="136" 
y="40"/><di:waypoint x="232" y="40"/></bpmndi:BPMNEdge>
+    </bpmndi:BPMNPlane>
+  </bpmndi:BPMNDiagram>
+
+</definitions>
diff --git 
a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessGenerator.java
 
b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessGenerator.java
index 00eda479eb1..85ec8510478 100644
--- 
a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessGenerator.java
+++ 
b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessGenerator.java
@@ -491,11 +491,10 @@ public class ProcessGenerator {
             for (Entry<String, String> subProcess : 
processMetaData.getSubProcesses().entrySet()) {
                 FieldDeclaration subprocessFieldDeclaration = new 
FieldDeclaration();
 
-                String fieldName = "process" + subProcess.getKey();
+                String fieldName = "process" + 
sanitizeClassName(subProcess.getKey());
+                String subProcessModelClass = packageName + "." + 
sanitizeClassName(subProcess.getKey() + "Model");
                 ClassOrInterfaceType modelType = new 
ClassOrInterfaceType(null, new 
SimpleName(org.kie.kogito.process.Process.class.getCanonicalName()),
-                        NodeList.nodeList(
-                                new ClassOrInterfaceType(null, 
processMetaData.getModelPackageName() != null ? 
processMetaData.getModelPackageName() + "." + 
processMetaData.getModelClassName()
-                                        : 
sanitizeClassName(subProcess.getKey() + "Model"))));
+                        NodeList.nodeList(new ClassOrInterfaceType(null, 
subProcessModelClass)));
                 if (context.hasDI()) {
                     subprocessFieldDeclaration
                             .addVariable(new VariableDeclarator(modelType, 
fieldName));
diff --git 
a/kogito-jbpm/jbpm-bpmn2/src/main/java/org/kie/kogito/process/bpmn2/StaticApplicationAssembler.java
 
b/kogito-jbpm/jbpm-bpmn2/src/main/java/org/kie/kogito/process/bpmn2/StaticApplicationAssembler.java
index 8241b89a33f..be2dc6b88fe 100644
--- 
a/kogito-jbpm/jbpm-bpmn2/src/main/java/org/kie/kogito/process/bpmn2/StaticApplicationAssembler.java
+++ 
b/kogito-jbpm/jbpm-bpmn2/src/main/java/org/kie/kogito/process/bpmn2/StaticApplicationAssembler.java
@@ -19,9 +19,7 @@
 package org.kie.kogito.process.bpmn2;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 import org.drools.io.ClassPathResource;
 import org.jbpm.bpmn2.xml.BPMNDISemanticModule;
@@ -29,19 +27,12 @@ import org.jbpm.bpmn2.xml.BPMNExtensionsSemanticModule;
 import org.jbpm.bpmn2.xml.BPMNSemanticModule;
 import org.jbpm.compiler.xml.XmlProcessReader;
 import org.jbpm.compiler.xml.core.SemanticModules;
-import org.jbpm.workflow.core.WorkflowProcess;
-import org.jbpm.workflow.core.impl.NodeIoHelper;
-import org.jbpm.workflow.core.node.SubProcessFactory;
-import org.jbpm.workflow.core.node.SubProcessNode;
-import org.jbpm.workflow.instance.impl.NodeInstanceImpl;
 import org.kie.api.definition.process.Process;
 import org.kie.api.io.Resource;
-import org.kie.api.runtime.process.ProcessContext;
 import org.kie.kogito.Application;
 import org.kie.kogito.StaticApplication;
 import org.kie.kogito.StaticConfig;
 import org.kie.kogito.process.ProcessConfig;
-import org.kie.kogito.process.ProcessInstance;
 import org.kie.kogito.process.ProcessInstancesFactory;
 
 public class StaticApplicationAssembler {
@@ -96,26 +87,6 @@ public class StaticApplicationAssembler {
                 .map(process -> new BpmnProcess(process, processConfig, 
application))
                 .forEach(container::addProcess);
 
-        // Initialize SubProcessFactory for all CallActivity nodes
-        container.processIds().forEach(processId -> {
-            BpmnProcess bpmnProcess = (BpmnProcess) 
container.processById(processId);
-            WorkflowProcess workflowProcess = (WorkflowProcess) 
bpmnProcess.process();
-
-            workflowProcess.getNodesRecursively().forEach(node -> {
-                if (node instanceof SubProcessNode) {
-                    SubProcessNode subProcessNode = (SubProcessNode) node;
-                    String subProcessId = subProcessNode.getProcessId();
-
-                    // Find the subprocess in the container
-                    org.kie.kogito.process.Process<?> subprocess = 
container.processById(subProcessId);
-                    if (subprocess != null) {
-                        subProcessNode.setSubProcessFactory(
-                                new 
BpmnSubProcessFactory((org.kie.kogito.process.Process<BpmnVariables>) 
subprocess));
-                    }
-                }
-            });
-        });
-
         return application;
     }
 
@@ -123,39 +94,4 @@ public class StaticApplicationAssembler {
         return INSTANCE;
     }
 
-    /**
-     * SubProcessFactory implementation for BPMN CallActivity nodes.
-     * Handles parameter binding between parent and subprocess.
-     */
-    private static class BpmnSubProcessFactory implements 
SubProcessFactory<BpmnVariables> {
-
-        private final org.kie.kogito.process.Process<BpmnVariables> subprocess;
-
-        BpmnSubProcessFactory(org.kie.kogito.process.Process<BpmnVariables> 
subprocess) {
-            this.subprocess = subprocess;
-        }
-
-        @Override
-        public BpmnVariables bind(ProcessContext kcontext) {
-            Map<String, Object> parameters = NodeIoHelper.processInputs(
-                    (NodeInstanceImpl) kcontext.getNodeInstance(),
-                    kcontext::getVariable);
-            return BpmnVariables.create(parameters);
-        }
-
-        @Override
-        public ProcessInstance<BpmnVariables> createInstance(BpmnVariables 
model) {
-            return subprocess.createInstance(model);
-        }
-
-        @Override
-        public void unbind(ProcessContext kcontext, BpmnVariables model) {
-            Map<String, Object> outputs = new HashMap<>(model.toMap());
-            NodeIoHelper.processOutputs(
-                    (NodeInstanceImpl) kcontext.getNodeInstance(),
-                    outputs::get,
-                    kcontext::getVariable);
-        }
-    }
-
 }
diff --git 
a/kogito-jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/LambdaSubProcessNodeVisitor.java
 
b/kogito-jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/LambdaSubProcessNodeVisitor.java
index f05c4f0591d..696b96088df 100644
--- 
a/kogito-jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/LambdaSubProcessNodeVisitor.java
+++ 
b/kogito-jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/LambdaSubProcessNodeVisitor.java
@@ -18,42 +18,14 @@
  */
 package org.jbpm.compiler.canonical;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-
+import org.jbpm.compiler.canonical.descriptors.ExpressionUtils;
 import org.jbpm.process.core.context.variable.VariableScope;
 import org.jbpm.ruleflow.core.factory.SubProcessNodeFactory;
-import org.jbpm.workflow.core.impl.DataDefinition;
 import org.jbpm.workflow.core.node.SubProcessNode;
-import org.jbpm.workflow.instance.impl.NodeInstanceImpl;
-import org.kie.kogito.internal.process.runtime.KogitoWorkflowProcess;
-import org.kie.kogito.process.ProcessInstance;
-import org.kie.kogito.process.Processes;
 
-import com.github.javaparser.ast.NodeList;
-import com.github.javaparser.ast.body.MethodDeclaration;
-import com.github.javaparser.ast.body.Parameter;
-import com.github.javaparser.ast.expr.AssignExpr;
 import com.github.javaparser.ast.expr.BooleanLiteralExpr;
-import com.github.javaparser.ast.expr.CastExpr;
-import com.github.javaparser.ast.expr.ClassExpr;
-import com.github.javaparser.ast.expr.Expression;
-import com.github.javaparser.ast.expr.LambdaExpr;
-import com.github.javaparser.ast.expr.MethodCallExpr;
-import com.github.javaparser.ast.expr.NameExpr;
-import com.github.javaparser.ast.expr.ObjectCreationExpr;
-import com.github.javaparser.ast.expr.StringLiteralExpr;
-import com.github.javaparser.ast.expr.VariableDeclarationExpr;
 import com.github.javaparser.ast.stmt.BlockStmt;
-import com.github.javaparser.ast.stmt.ReturnStmt;
-import com.github.javaparser.ast.type.ClassOrInterfaceType;
 
-import static com.github.javaparser.StaticJavaParser.parse;
-import static com.github.javaparser.ast.NodeList.nodeList;
-import static org.drools.util.StringUtils.ucFirst;
 import static 
org.jbpm.ruleflow.core.factory.SubProcessNodeFactory.METHOD_INDEPENDENT;
 import static 
org.jbpm.ruleflow.core.factory.SubProcessNodeFactory.METHOD_PROCESS_ID;
 import static 
org.jbpm.ruleflow.core.factory.SubProcessNodeFactory.METHOD_PROCESS_NAME;
@@ -72,12 +44,6 @@ public class LambdaSubProcessNodeVisitor extends 
AbstractNodeVisitor<SubProcessN
 
     @Override
     public void visitNode(String factoryField, SubProcessNode node, BlockStmt 
body, VariableScope variableScope, ProcessMetaData metadata) {
-        Optional<Expression> retValue;
-        try (InputStream resourceAsStream = 
this.getClass().getResourceAsStream("/class-templates/SubProcessFactoryTemplate.java"))
 {
-            retValue = parse(resourceAsStream).findFirst(Expression.class);
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
         String name = node.getName();
         String subProcessId = node.getProcessId();
 
@@ -87,175 +53,18 @@ public class LambdaSubProcessNodeVisitor extends 
AbstractNodeVisitor<SubProcessN
 
         body.addStatement(getAssignedFactoryMethod(factoryField, 
SubProcessNodeFactory.class, getNodeId(node), getNodeKey(), 
getWorkflowElementConstructor(node.getId())))
                 .addStatement(getNameMethod(node, "Call Activity"))
-                .addStatement(getFactoryMethod(getNodeId(node), 
METHOD_PROCESS_ID, new StringLiteralExpr(subProcessId)))
-                .addStatement(getFactoryMethod(getNodeId(node), 
METHOD_PROCESS_NAME, new StringLiteralExpr(getOrDefault(node.getProcessName(), 
""))))
+                .addStatement(getFactoryMethod(getNodeId(node), 
METHOD_PROCESS_ID, ExpressionUtils.getLiteralExpr(subProcessId)))
+                .addStatement(getFactoryMethod(getNodeId(node), 
METHOD_PROCESS_NAME, 
ExpressionUtils.getLiteralExpr(getOrDefault(node.getProcessName(), ""))))
                 .addStatement(getFactoryMethod(getNodeId(node), 
METHOD_WAIT_FOR_COMPLETION, new BooleanLiteralExpr(node.isWaitForCompletion())))
                 .addStatement(getFactoryMethod(getNodeId(node), 
METHOD_INDEPENDENT, new BooleanLiteralExpr(node.isIndependent())));
 
-        Map<String, String> inputTypes = 
node.getIoSpecification().getInputTypes();
-
-        String subProcessModelClassName = metadata.getModelClassName() != null 
? metadata.getModelClassName() : 
ProcessToExecModelGenerator.extractModelClassName(subProcessId);
-
-        ModelMetaData subProcessModel = new ModelMetaData(subProcessId,
-                metadata.getModelPackageName() != null ? 
metadata.getModelPackageName() : metadata.getPackageName(),
-                subProcessModelClassName,
-                KogitoWorkflowProcess.PRIVATE_VISIBILITY,
-                VariableDeclarations.ofRawInfo(inputTypes),
-                false);
-
-        retValue.ifPresentOrElse(retValueExpression -> {
-
-            retValueExpression.findAll(ClassOrInterfaceType.class)
-                    .stream()
-                    .filter(t -> t.getNameAsString().equals("$Type$"))
-                    .forEach(t -> t.setName(subProcessModelClassName));
-
-            retValueExpression.findFirst(MethodDeclaration.class, m -> 
m.getNameAsString().equals("bind"))
-                    .ifPresent(m -> m.setBody(bind(subProcessModel)));
-            retValueExpression.findFirst(MethodDeclaration.class, m -> 
m.getNameAsString().equals("createInstance"))
-                    .ifPresent(m -> m.setBody(createInstance(node, metadata)));
-            retValueExpression.findFirst(MethodDeclaration.class, m -> 
m.getNameAsString().equals("unbind"))
-                    .ifPresent(m -> m.setBody(unbind(node)));
-            body.addStatement(getFactoryMethod(getNodeId(node), getNodeKey(), 
retValueExpression));
-
-        }, () -> body.addStatement(getFactoryMethod(getNodeId(node), 
getNodeKey())));
-
         addNodeMappings(node, body, getNodeId(node));
         visitMetaData(node.getMetaData(), body, getNodeId(node));
         body.addStatement(getDoneMethod(getNodeId(node)));
-    }
-
-    private BlockStmt bind(ModelMetaData subProcessModel) {
-        BlockStmt actionBody = new BlockStmt();
-        actionBody.addStatement(subProcessModel.newInstance("model"));
-
-        // process the inputs of the task
-        ClassOrInterfaceType nodeInstanceType = new ClassOrInterfaceType(null, 
NodeInstanceImpl.class.getCanonicalName());
-        ClassOrInterfaceType objectType = new ClassOrInterfaceType(null, 
Object.class.getCanonicalName());
-        ClassOrInterfaceType stringType = new ClassOrInterfaceType(null, 
String.class.getCanonicalName());
-        ClassOrInterfaceType type = new ClassOrInterfaceType(null, 
Map.class.getCanonicalName()).setTypeArguments(nodeList(stringType, 
objectType));
-        VariableDeclarationExpr expr = new VariableDeclarationExpr(type, 
"inputs");
-
-        BlockStmt lambdaBody = new BlockStmt();
-        MethodCallExpr getVariableExpr = new MethodCallExpr(new 
NameExpr(KCONTEXT_VAR), "getVariable").addArgument(new NameExpr("name"));
-        Expression getNodeInstance = new CastExpr(nodeInstanceType, new 
MethodCallExpr(new NameExpr(KCONTEXT_VAR), "getNodeInstance"));
-        lambdaBody.addStatement(new ReturnStmt(getVariableExpr));
-        Parameter varName = new Parameter(stringType, "name");
-        LambdaExpr sourceResolverExpr = new LambdaExpr(nodeList(varName), 
lambdaBody);
-
-        MethodCallExpr processInputsExpr = new MethodCallExpr(null, 
"org.jbpm.workflow.core.impl.NodeIoHelper.processInputs", 
nodeList(getNodeInstance, sourceResolverExpr));
-        AssignExpr inputs = new AssignExpr(expr, processInputsExpr, 
AssignExpr.Operator.ASSIGN);
-        actionBody.addStatement(inputs);
-
-        actionBody.addStatement(subProcessModel.callUpdateFromMap("model", 
"inputs"));
-        actionBody.addStatement(new ReturnStmt(new NameExpr("model")));
-        return actionBody;
-    }
-
-    private BlockStmt createInstance(SubProcessNode subProcessNode, 
ProcessMetaData metadata) {
-        String processId = 
ProcessToExecModelGenerator.extractProcessId(subProcessNode.getProcessId());
-        String subProcessModelClassName = metadata.getModelClassName() != null 
? metadata.getModelClassName() : 
ProcessToExecModelGenerator.extractModelClassName(processId);
-        String processFieldName = "app";
-        Expression expr = new NameExpr(processFieldName);
-        ClassOrInterfaceType processesType = new ClassOrInterfaceType(null, 
Processes.class.getCanonicalName());
-        expr = new MethodCallExpr(expr, "get", NodeList.nodeList(new 
ClassExpr(processesType)));
-        expr = new MethodCallExpr(expr, "processById", NodeList.nodeList(new 
StringLiteralExpr(subProcessNode.getProcessId())));
-        expr = new MethodCallExpr(expr, "createInstance").addArgument("model");
-        ClassOrInterfaceType subProcessType = new ClassOrInterfaceType(null, 
ProcessInstance.class.getCanonicalName());
-        subProcessType.setTypeArguments(new ClassOrInterfaceType(null, 
subProcessModelClassName));
-        expr = new CastExpr(subProcessType, expr);
-
-        metadata.addSubProcess(processId, subProcessNode.getProcessId());
-
-        return new BlockStmt().addStatement(new ReturnStmt(expr));
-    }
 
-    private BlockStmt unbind(SubProcessNode subProcessNode) {
-        BlockStmt actionBody = new BlockStmt();
-
-        // process the outputs of the task
-        ClassOrInterfaceType nodeInstanceType = new ClassOrInterfaceType(null, 
NodeInstanceImpl.class.getCanonicalName());
-        ClassOrInterfaceType objectType = new ClassOrInterfaceType(null, 
Object.class.getCanonicalName());
-        ClassOrInterfaceType stringType = new ClassOrInterfaceType(null, 
String.class.getCanonicalName());
-        ClassOrInterfaceType type = new ClassOrInterfaceType(null, 
Map.class.getCanonicalName()).setTypeArguments(nodeList(stringType, 
objectType));
-        ClassOrInterfaceType hashMapType = new ClassOrInterfaceType(null, 
HashMap.class.getCanonicalName()).setTypeArguments(nodeList(stringType, 
objectType));
-
-        // we get the outputs from model
-        VariableDeclarationExpr expr = new VariableDeclarationExpr(type, 
"outputs");
-        actionBody.addStatement(new AssignExpr(expr, new 
ObjectCreationExpr(null, hashMapType, nodeList()), AssignExpr.Operator.ASSIGN));
-        // do the actual assignments
-        for (DataDefinition outputDefinition : 
subProcessNode.getIoSpecification().getDataOutput().values()) {
-            // remove multiinstance data. It does not belong to this model it 
is just for calculations with
-            // data associations
-            String collectionOutput = (String) 
subProcessNode.getMetaData().get("MICollectionOutput");
-            if (collectionOutput != null && 
collectionOutput.equals(outputDefinition.getLabel())) {
-                continue;
-            }
-            DataDefinition multiInstance = 
subProcessNode.getMultiInstanceSpecification().getOutputDataItem();
-            if (multiInstance != null && 
multiInstance.getLabel().equals(outputDefinition.getLabel())) {
-                continue;
-            }
-
-            Expression getValueExpr = new MethodCallExpr(new 
NameExpr("model"), "get" + ucFirst(outputDefinition.getLabel()));
-            Expression setValueExpr = new MethodCallExpr(new 
NameExpr("outputs"), "put", nodeList(new 
StringLiteralExpr(outputDefinition.getLabel()), getValueExpr));
-            actionBody.addStatement(setValueExpr);
-        }
-
-        // source resolver
-        BlockStmt lambdaSourceBody = new BlockStmt();
-        Expression getSourceVariableExpr = new MethodCallExpr(new 
NameExpr("outputs"), "get", nodeList(new NameExpr("name")));
-        lambdaSourceBody.addStatement(new ReturnStmt(getSourceVariableExpr));
-        LambdaExpr sourceResolverExpr = new LambdaExpr(nodeList(new 
Parameter(stringType, "name")), lambdaSourceBody);
-
-        // target resolver
-        BlockStmt lambdaTargetBody = new BlockStmt();
-        Expression getTargetVariableExpr = new MethodCallExpr(new 
NameExpr(KCONTEXT_VAR), "getVariable").addArgument(new NameExpr("name"));
-        lambdaTargetBody.addStatement(new ReturnStmt(getTargetVariableExpr));
-        LambdaExpr targetResolverExpr = new LambdaExpr(nodeList(new 
Parameter(stringType, "name")), lambdaTargetBody);
-
-        Expression getNodeInstance = new CastExpr(nodeInstanceType, new 
MethodCallExpr(new NameExpr(KCONTEXT_VAR), "getNodeInstance"));
-        MethodCallExpr processOutputsExpr = new MethodCallExpr(null, 
"org.jbpm.workflow.core.impl.NodeIoHelper.processOutputs", 
nodeList(getNodeInstance, sourceResolverExpr, targetResolverExpr));
-
-        actionBody.addStatement(processOutputsExpr);
-
-        return actionBody;
-    }
-
-    protected Expression dotNotationToSetExpression(String dotNotation, String 
value) {
-        String[] elements = dotNotation.split("\\.");
-        Expression scope = new NameExpr(elements[0]);
-        if (elements.length == 1) {
-            return new AssignExpr(
-                    scope,
-                    new NameExpr(value),
-                    AssignExpr.Operator.ASSIGN);
-        }
-        for (int i = 1; i < elements.length - 1; i++) {
-            scope = new MethodCallExpr()
-                    .setScope(scope)
-                    .setName("get" + ucFirst(elements[i]));
-        }
-
-        return new MethodCallExpr()
-                .setScope(scope)
-                .setName("set" + ucFirst(elements[elements.length - 1]))
-                .addArgument(value);
-    }
-
-    protected Expression dotNotationToGetExpression(String dotNotation) {
-        String[] elements = dotNotation.split("\\.");
-        Expression scope = new NameExpr(elements[0]);
-
-        if (elements.length == 1) {
-            return scope;
+        if (subProcessId != null && !subProcessId.contains("#{")) {
+            String processId = 
ProcessToExecModelGenerator.extractProcessId(subProcessId);
+            metadata.addSubProcess(processId, subProcessId);
         }
-
-        for (int i = 1; i < elements.length; i++) {
-            scope = new MethodCallExpr()
-                    .setScope(scope)
-                    .setName("get" + ucFirst(elements[i]));
-        }
-
-        return scope;
     }
 }
diff --git 
a/kogito-jbpm/jbpm-flow-builder/src/main/resources/class-templates/SubProcessFactoryTemplate.java
 
b/kogito-jbpm/jbpm-flow-builder/src/main/resources/class-templates/SubProcessFactoryTemplate.java
deleted file mode 100644
index ff1eebc3cbd..00000000000
--- 
a/kogito-jbpm/jbpm-flow-builder/src/main/resources/class-templates/SubProcessFactoryTemplate.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-class Template {
-    Object f = new org.jbpm.workflow.core.node.SubProcessFactory<$Type$>() {
-        public $Type$ bind(org.kie.api.runtime.process.ProcessContext 
kcontext) {
-            return null;
-        }
-        public org.kie.kogito.process.ProcessInstance<$Type$> 
createInstance($Type$ model) {
-            return null;
-        }
-        public void unbind(org.kie.api.runtime.process.ProcessContext 
kcontext, $Type$ model) {
-
-        }
-    };
-}
diff --git 
a/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/ruleflow/core/factory/SubProcessNodeFactory.java
 
b/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/ruleflow/core/factory/SubProcessNodeFactory.java
index 574b8e498d2..5f5e806010b 100755
--- 
a/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/ruleflow/core/factory/SubProcessNodeFactory.java
+++ 
b/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/ruleflow/core/factory/SubProcessNodeFactory.java
@@ -22,10 +22,8 @@ import org.jbpm.process.core.ContextContainer;
 import org.jbpm.process.core.context.variable.Mappable;
 import org.jbpm.ruleflow.core.RuleFlowNodeContainerFactory;
 import org.jbpm.workflow.core.NodeContainer;
-import org.jbpm.workflow.core.node.SubProcessFactory;
 import org.jbpm.workflow.core.node.SubProcessNode;
 import org.kie.api.definition.process.WorkflowElementIdentifier;
-import org.kie.kogito.Model;
 
 public class SubProcessNodeFactory<T extends RuleFlowNodeContainerFactory<T, 
?>> extends StateBasedNodeFactory<SubProcessNodeFactory<T>, T>
         implements MappableNodeFactory<SubProcessNodeFactory<T>>, 
ContextContainerFactory<SubProcessNodeFactory<T>> {
@@ -68,11 +66,6 @@ public class SubProcessNodeFactory<T extends 
RuleFlowNodeContainerFactory<T, ?>>
         return this;
     }
 
-    public SubProcessNodeFactory<T> subProcessNode(SubProcessFactory<? extends 
Model> factory) {
-        getSubProcessNode().setSubProcessFactory(factory);
-        return this;
-    }
-
     @Override
     public ContextContainer getContextNode() {
         return getSubProcessNode();
diff --git 
a/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/SubProcessFactory.java
 
b/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/SubProcessFactory.java
deleted file mode 100644
index 789ca166505..00000000000
--- 
a/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/SubProcessFactory.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.jbpm.workflow.core.node;
-
-import org.kie.api.runtime.process.ProcessContext;
-import org.kie.kogito.process.ProcessInstance;
-
-public interface SubProcessFactory<T> {
-    T bind(ProcessContext ctx);
-
-    ProcessInstance<T> createInstance(T model);
-
-    void unbind(ProcessContext ctx, T model);
-}
diff --git 
a/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/SubProcessNode.java
 
b/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/SubProcessNode.java
index d3711219a4f..ef13ee68eba 100755
--- 
a/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/SubProcessNode.java
+++ 
b/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/SubProcessNode.java
@@ -46,7 +46,6 @@ public class SubProcessNode extends StateBasedNode implements 
ContextContainer {
     private boolean waitForCompletion = true;
 
     private boolean independent = true;
-    private SubProcessFactory<?> subProcessFactory;
 
     public SubProcessNode() {
         super(NodeType.SUBPROCESS);
@@ -153,13 +152,4 @@ public class SubProcessNode extends StateBasedNode 
implements ContextContainer {
         }
         return Boolean.parseBoolean(abortParent);
     }
-
-    public <T> void setSubProcessFactory(
-            SubProcessFactory<T> subProcessFactory) {
-        this.subProcessFactory = subProcessFactory;
-    }
-
-    public SubProcessFactory<?> getSubProcessFactory() {
-        return subProcessFactory;
-    }
 }
diff --git 
a/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/NodeInstanceImpl.java
 
b/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/NodeInstanceImpl.java
index 2dc6622ab32..fdb4dfd9d1a 100755
--- 
a/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/NodeInstanceImpl.java
+++ 
b/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/NodeInstanceImpl.java
@@ -711,6 +711,16 @@ public abstract class NodeInstanceImpl implements 
org.jbpm.workflow.instance.Nod
         return expression != null && 
PatternConstants.PARAMETER_MATCHER.matcher(expression).find();
     }
 
+    /**
+     * Extracts the first variable/expression name from a #{...} template.
+     * For example, {@code "#{processName}"} returns {@code "processName"} and
+     * {@code "#{a + b}-suffix"} returns {@code "a + b"}.
+     */
+    protected static String extractFirstVariableName(String expression) {
+        Matcher m = PatternConstants.PARAMETER_MATCHER.matcher(expression);
+        return m.find() ? m.group(1) : expression;
+    }
+
     public String resolveExpression(String expression) {
         return isExpression(expression) ? (String) resolveValue(expression) : 
expression;
     }
diff --git 
a/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/LambdaSubProcessNodeInstance.java
 
b/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/LambdaSubProcessNodeInstance.java
index f6851885d26..7220d0c72c6 100755
--- 
a/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/LambdaSubProcessNodeInstance.java
+++ 
b/kogito-jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/LambdaSubProcessNodeInstance.java
@@ -38,9 +38,8 @@ import org.jbpm.process.instance.impl.ContextInstanceFactory;
 import org.jbpm.process.instance.impl.ContextInstanceFactoryRegistry;
 import org.jbpm.process.instance.impl.ProcessInstanceImpl;
 import org.jbpm.ruleflow.core.Metadata;
-import org.jbpm.util.ContextFactory;
 import org.jbpm.workflow.core.Node;
-import org.jbpm.workflow.core.node.SubProcessFactory;
+import org.jbpm.workflow.core.impl.NodeIoHelper;
 import org.jbpm.workflow.core.node.SubProcessNode;
 import org.kie.kogito.Model;
 import org.kie.kogito.internal.process.event.KogitoEventListener;
@@ -48,6 +47,7 @@ import 
org.kie.kogito.internal.process.runtime.KogitoNodeInstance;
 import org.kie.kogito.internal.process.runtime.KogitoProcessInstance;
 import org.kie.kogito.internal.process.runtime.KogitoProcessRuntime;
 import org.kie.kogito.process.MutableProcessInstances;
+import org.kie.kogito.process.Process;
 import org.kie.kogito.process.Processes;
 import org.kie.kogito.process.impl.AbstractProcessInstance;
 import org.slf4j.Logger;
@@ -84,12 +84,22 @@ public class LambdaSubProcessNodeInstance extends 
StateBasedNodeInstance impleme
                     "A SubProcess node only accepts default incoming 
connections!");
         }
 
-        KogitoProcessContextImpl context = ContextFactory.fromNode(this);
-        SubProcessFactory subProcessFactory = 
getSubProcessNode().getSubProcessFactory();
-        Object o = subProcessFactory.bind(context);
-        org.kie.kogito.process.ProcessInstance<?> processInstance = 
subProcessFactory.createInstance(o);
+        String processId = resolveProcessId();
+        ProcessInstance processInstance = (ProcessInstance) 
getProcessInstance();
+        KogitoProcessRuntime kruntime = (KogitoProcessRuntime) 
processInstance.getKnowledgeRuntime();
+        Process process = 
kruntime.getApplication().get(Processes.class).processById(processId);
 
-        ProcessInstanceImpl pi = (ProcessInstanceImpl) 
((AbstractProcessInstance<?>) processInstance).internalGetProcessInstance();
+        if (process == null) {
+            throw new IllegalArgumentException("Cannot find process with id " 
+ processId);
+        }
+
+        Map<String, Object> parameters = NodeIoHelper.processInputs(this, key 
-> getVariable(key));
+
+        Model model = (Model) process.createModel();
+        model.update(parameters);
+        org.kie.kogito.process.ProcessInstance<?> subProcessInstance = 
process.createInstance(model);
+
+        ProcessInstanceImpl pi = (ProcessInstanceImpl) 
((AbstractProcessInstance<?>) subProcessInstance).internalGetProcessInstance();
         pi.setMetaData("ParentProcessInstanceId", 
getProcessInstance().getStringId());
         pi.setMetaData("ParentNodeInstanceId", getUniqueId());
         pi.setMetaData("ParentNodeId", getSubProcessNode().getUniqueId());
@@ -103,18 +113,22 @@ public class LambdaSubProcessNodeInstance extends 
StateBasedNodeInstance impleme
             logger.debug("Parent headers are {}", headers != null ? 
headers.keySet() : Set.of());
         }
         pi.setHeaders(headers);
-        // headers parameters set to null so start does not override the ones 
already set
-        processInstance.start(null);
-        this.processInstanceId = processInstance.id();
-        this.asyncWaitingNodeInstance = hasAsyncNodeInstance(pi);
 
-        subProcessFactory.unbind(context, processInstance.variables());
+        subProcessInstance.start();
+
+        this.processInstanceId = subProcessInstance.id();
+        this.asyncWaitingNodeInstance = hasAsyncNodeInstance(pi);
 
         if (!getSubProcessNode().isWaitForCompletion()) {
             triggerCompleted();
-        } else if (processInstance.status() == 
KogitoProcessInstance.STATE_COMPLETED || processInstance.status() == 
KogitoProcessInstance.STATE_ABORTED) {
+        } else if (subProcessInstance.status() == 
KogitoProcessInstance.STATE_COMPLETED || subProcessInstance.status() == 
KogitoProcessInstance.STATE_ABORTED) {
+            // Subprocess completed synchronously - handle output mappings 
immediately
+            Map<String, Object> outputSet = new HashMap<>(((Model) 
subProcessInstance.variables()).toMap());
+            NodeIoHelper.processOutputs(this, varRef -> outputSet.get(varRef), 
varName -> this.getVariable(varName));
+
             processInstanceCompleted((ProcessInstance) pi);
         } else {
+
             addProcessListener();
         }
     }
@@ -139,8 +153,9 @@ public class LambdaSubProcessNodeInstance extends 
StateBasedNodeInstance impleme
         }
 
         KogitoProcessRuntime kruntime = (KogitoProcessRuntime) 
((ProcessInstance) getProcessInstance()).getKnowledgeRuntime();
+        String processId = resolveProcessId();
         Optional<AbstractProcessInstance> pi =
-                ((MutableProcessInstances) 
kruntime.getApplication().get(Processes.class).processById(this.getSubProcessNode().getProcessId()).instances()).findById(processInstanceId);
+                ((MutableProcessInstances) 
kruntime.getApplication().get(Processes.class).processById(processId).instances()).findById(processInstanceId);
         pi.ifPresent(e -> e.abort());
     }
 
@@ -192,7 +207,6 @@ public class LambdaSubProcessNodeInstance extends 
StateBasedNodeInstance impleme
         processInstanceId = null;
         if (processInstance.getState() == KogitoProcessInstance.STATE_ABORTED) 
{
             String faultName = processInstance.getOutcome() == null ? "" : 
processInstance.getOutcome();
-            // handle exception as sub process failed with error code
             ExceptionScopeInstance exceptionScopeInstance = 
(ExceptionScopeInstance) resolveContextInstance(ExceptionScope.EXCEPTION_SCOPE, 
faultName);
             if (exceptionScopeInstance != null) {
                 KogitoProcessContextImpl context = new 
KogitoProcessContextImpl(this.getProcessInstance().getKnowledgeRuntime());
@@ -222,13 +236,8 @@ public class LambdaSubProcessNodeInstance extends 
StateBasedNodeInstance impleme
     @SuppressWarnings({ "unchecked", "rawtypes" })
     private void handleOutMappings(ProcessInstance processInstance) {
 
-        SubProcessFactory subProcessFactory = 
getSubProcessNode().getSubProcessFactory();
-        org.kie.kogito.process.ProcessInstance<?> pi = 
((org.kie.kogito.process.ProcessInstance<?>) processInstance.unwrap());
-        if (pi != null) {
-            Model model = (Model) pi.process().createModel();
-            model.fromMap(processInstance.getVariables());
-            subProcessFactory.unbind(ContextFactory.fromNode(this), model);
-        }
+        Map<String, Object> outputSet = processInstance.getVariables();
+        NodeIoHelper.processOutputs(this, varRef -> outputSet.get(varRef), 
varName -> this.getVariable(varName));
     }
 
     @Override
@@ -294,4 +303,46 @@ public class LambdaSubProcessNodeInstance extends 
StateBasedNodeInstance impleme
         return getSubProcessNode();
     }
 
+    /**
+     * Resolves the process ID, supporting variable substitution and MVEL 
expressions.
+     *
+     * Leverages the inherited {@link 
NodeInstanceImpl#resolveExpression(String)} method
+     * which supports multiple {@code #{...}} tokens in one string, e.g.:
+     * <ul>
+     * <li>Static process IDs: {@code "order-subprocess"}</li>
+     * <li>Simple variables: {@code "#{processName}"}</li>
+     * <li>Property access: {@code "#{order.type}"}</li>
+     * <li>Multiple expressions: {@code "#{prefix}-#{suffix}"}</li>
+     * <li>Mixed literals and expressions: {@code "#{order.type}-process"}</li>
+     * </ul>
+     *
+     * @return the resolved process ID
+     * @throws IllegalArgumentException if the expression contains 
unresolvable variables
+     * @see NodeInstanceImpl#resolveExpression(String)
+     */
+    private String resolveProcessId() {
+        String processId = getSubProcessNode().getProcessId();
+
+        if (processId == null) {
+            return null;
+        }
+
+        String resolvedId = resolveExpression(processId);
+
+        if (isExpression(processId)) {
+            if (resolvedId == null || resolvedId.trim().isEmpty() || 
resolvedId.contains("#{")) {
+                throw new IllegalArgumentException(
+                        "Cannot resolve subprocess ID from expression '" + 
processId + "': "
+                                + "Variable '" + 
extractFirstVariableName(processId)
+                                + "' not found in process context or is 
empty");
+            }
+        }
+
+        if (logger.isDebugEnabled() && !processId.equals(resolvedId)) {
+            logger.debug("Resolved process ID from '{}' to '{}'", processId, 
resolvedId);
+        }
+
+        return resolvedId;
+    }
+
 }
diff --git 
a/kogito-jbpm/jbpm-flow/src/test/java/org/jbpm/process/test/NodeCreator.java 
b/kogito-jbpm/jbpm-flow/src/test/java/org/jbpm/process/test/NodeCreator.java
index 6233e97abb8..d83deeeac23 100755
--- a/kogito-jbpm/jbpm-flow/src/test/java/org/jbpm/process/test/NodeCreator.java
+++ b/kogito-jbpm/jbpm-flow/src/test/java/org/jbpm/process/test/NodeCreator.java
@@ -38,9 +38,9 @@ public class NodeCreator<T extends NodeImpl> {
     public NodeCreator(NodeContainer nodeContainer, Class<T> clazz) {
         this.nodeContainer = nodeContainer;
         try {
-            this.constructor = clazz.getConstructor();
+            this.constructor = (Constructor<T>) clazz.getConstructor();
         } catch (NoSuchMethodException e) {
-            throw new IllegalArgumentException("No public no-arg constructor 
found on " + clazz.getName(), e);
+            throw new RuntimeException("No public no-arg constructor found for 
" + clazz.getName(), e);
         }
     }
 
diff --git 
a/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityByExpression.bpmn2
 
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityByExpression.bpmn2
new file mode 100644
index 00000000000..56cc10c3ee5
--- /dev/null
+++ 
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityByExpression.bpmn2
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<definitions id="Definition"
+             targetNamespace="http://www.example.org/MinimalExample";
+             typeLanguage="http://www.java.com/javaTypes";
+             expressionLanguage="http://www.mvel.org/2.0";
+             xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL";
+             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+             xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL 
BPMN20.xsd"
+             xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI";
+             xmlns:dc="http://www.omg.org/spec/DD/20100524/DC";
+             xmlns:di="http://www.omg.org/spec/DD/20100524/DI";
+             xmlns:tns="http://www.jboss.org/drools";>
+
+  <itemDefinition id="_prefixItem" structureRef="String"/>
+  <itemDefinition id="_xItem"      structureRef="String"/>
+  <itemDefinition id="_yItem"      structureRef="String"/>
+
+  <!-- calledElement is a MVEL expression: prefix + literal suffix -->
+  <process processType="Private" isExecutable="true"
+           id="DynamicCallActivityByExpression"
+           name="Dynamic Call Activity By Expression"
+           tns:packageName="org.jbpm.bpmn2.subprocess">
+
+    <property id="prefix" itemSubjectRef="_prefixItem"/>
+    <property id="x"      itemSubjectRef="_xItem"/>
+    <property id="y"      itemSubjectRef="_yItem"/>
+
+    <startEvent id="_1" name="Start"/>
+    <callActivity id="_2" name="DynamicSubProcess" calledElement="#{prefix + 
'SubProcess'}">
+      <ioSpecification>
+        <dataInput  id="_2_subXInput"  name="subX"/>
+        <dataOutput id="_2_subYOutput" name="subY"/>
+        <inputSet>
+          <dataInputRefs>_2_subXInput</dataInputRefs>
+        </inputSet>
+        <outputSet>
+          <dataOutputRefs>_2_subYOutput</dataOutputRefs>
+        </outputSet>
+      </ioSpecification>
+      <dataInputAssociation>
+        <sourceRef>x</sourceRef>
+        <targetRef>_2_subXInput</targetRef>
+      </dataInputAssociation>
+      <dataOutputAssociation>
+        <sourceRef>_2_subYOutput</sourceRef>
+        <targetRef>y</targetRef>
+      </dataOutputAssociation>
+    </callActivity>
+    <endEvent id="_3" name="End"/>
+
+    <sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2"/>
+    <sequenceFlow id="_2-_3" sourceRef="_2" targetRef="_3"/>
+  </process>
+
+</definitions>
diff --git 
a/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityByVariable.bpmn2
 
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityByVariable.bpmn2
new file mode 100644
index 00000000000..3e4db04b29f
--- /dev/null
+++ 
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityByVariable.bpmn2
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<definitions id="Definition"
+             targetNamespace="http://www.example.org/MinimalExample";
+             typeLanguage="http://www.java.com/javaTypes";
+             expressionLanguage="http://www.mvel.org/2.0";
+             xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL";
+             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+             xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL 
BPMN20.xsd"
+             xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI";
+             xmlns:dc="http://www.omg.org/spec/DD/20100524/DC";
+             xmlns:di="http://www.omg.org/spec/DD/20100524/DI";
+             xmlns:tns="http://www.jboss.org/drools";>
+
+  <itemDefinition id="_subprocessIdItem" structureRef="String"/>
+  <itemDefinition id="_xItem" structureRef="String"/>
+  <itemDefinition id="_yItem" structureRef="String"/>
+
+  <!-- Parent process: calledElement is a variable expression resolved at 
runtime -->
+  <process processType="Private" isExecutable="true"
+           id="DynamicCallActivityByVariable"
+           name="Dynamic Call Activity By Variable"
+           tns:version="1"
+           tns:packageName="org.jbpm.bpmn2.subprocess">
+
+    <property id="subprocessId" itemSubjectRef="_subprocessIdItem"/>
+    <property id="x"            itemSubjectRef="_xItem"/>
+    <property id="y"            itemSubjectRef="_yItem"/>
+
+    <startEvent id="_1" name="Start"/>
+    <callActivity id="_2" name="DynamicSubProcess" 
calledElement="#{subprocessId}">
+      <ioSpecification>
+        <dataInput  id="_2_subXInput"  name="subX"/>
+        <dataOutput id="_2_subYOutput" name="subY"/>
+        <inputSet>
+          <dataInputRefs>_2_subXInput</dataInputRefs>
+        </inputSet>
+        <outputSet>
+          <dataOutputRefs>_2_subYOutput</dataOutputRefs>
+        </outputSet>
+      </ioSpecification>
+      <dataInputAssociation>
+        <sourceRef>x</sourceRef>
+        <targetRef>_2_subXInput</targetRef>
+      </dataInputAssociation>
+      <dataOutputAssociation>
+        <sourceRef>_2_subYOutput</sourceRef>
+        <targetRef>y</targetRef>
+      </dataOutputAssociation>
+    </callActivity>
+    <endEvent id="_3" name="End"/>
+
+    <sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2"/>
+    <sequenceFlow id="_2-_3" sourceRef="_2" targetRef="_3"/>
+  </process>
+
+</definitions>
diff --git 
a/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityMissingVariable.bpmn2
 
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityMissingVariable.bpmn2
new file mode 100644
index 00000000000..1fb3e97c125
--- /dev/null
+++ 
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityMissingVariable.bpmn2
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<definitions id="Definition"
+             targetNamespace="http://www.example.org/MinimalExample";
+             typeLanguage="http://www.java.com/javaTypes";
+             expressionLanguage="http://www.mvel.org/2.0";
+             xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL";
+             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+             xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL 
BPMN20.xsd"
+             xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI";
+             xmlns:dc="http://www.omg.org/spec/DD/20100524/DC";
+             xmlns:di="http://www.omg.org/spec/DD/20100524/DI";
+             xmlns:tns="http://www.jboss.org/drools";>
+
+  <!-- calledElement references a variable that is never declared as a process 
variable -->
+  <process processType="Private" isExecutable="true"
+           id="DynamicCallActivityMissingVariable"
+           name="Dynamic Call Activity Missing Variable"
+           tns:packageName="org.jbpm.bpmn2.subprocess">
+
+    <!-- NOTE: no <property> for 'missingVar' — it is intentionally absent -->
+
+    <startEvent id="_1" name="Start"/>
+    <callActivity id="_2" name="DynamicSubProcessMissing" 
calledElement="#{missingVar}"/>
+    <endEvent id="_3" name="End"/>
+
+    <sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2"/>
+    <sequenceFlow id="_2-_3" sourceRef="_2" targetRef="_3"/>
+  </process>
+
+</definitions>
diff --git 
a/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityMultiExpression.bpmn2
 
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityMultiExpression.bpmn2
new file mode 100644
index 00000000000..e5f974d5abc
--- /dev/null
+++ 
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityMultiExpression.bpmn2
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<definitions id="Definition"
+             targetNamespace="http://www.example.org/MinimalExample";
+             typeLanguage="http://www.java.com/javaTypes";
+             expressionLanguage="http://www.mvel.org/2.0";
+             xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL";
+             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+             xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL 
BPMN20.xsd"
+             xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI";
+             xmlns:dc="http://www.omg.org/spec/DD/20100524/DC";
+             xmlns:di="http://www.omg.org/spec/DD/20100524/DI";
+             xmlns:tns="http://www.jboss.org/drools";>
+
+  <itemDefinition id="_prefixItem"  structureRef="String"/>
+  <itemDefinition id="_suffixItem"  structureRef="String"/>
+  <itemDefinition id="_xItem"       structureRef="String"/>
+  <itemDefinition id="_yItem"       structureRef="String"/>
+
+  <!-- calledElement composed from two independent variable expressions:
+       "#{prefix}#{suffix}" e.g. "CallActivity" + "SubProcess" = 
"CallActivitySubProcess" -->
+  <process processType="Private" isExecutable="true"
+           id="DynamicCallActivityMultiExpression"
+           name="Dynamic Call Activity Multi Expression"
+           tns:packageName="org.jbpm.bpmn2.subprocess">
+
+    <property id="prefix" itemSubjectRef="_prefixItem"/>
+    <property id="suffix" itemSubjectRef="_suffixItem"/>
+    <property id="x"      itemSubjectRef="_xItem"/>
+    <property id="y"      itemSubjectRef="_yItem"/>
+
+    <startEvent id="_1" name="Start"/>
+    <callActivity id="_2" name="DynamicSubProcess" 
calledElement="#{prefix}#{suffix}">
+      <ioSpecification>
+        <dataInput  id="_2_subXInput"  name="subX"/>
+        <dataOutput id="_2_subYOutput" name="subY"/>
+        <inputSet>
+          <dataInputRefs>_2_subXInput</dataInputRefs>
+        </inputSet>
+        <outputSet>
+          <dataOutputRefs>_2_subYOutput</dataOutputRefs>
+        </outputSet>
+      </ioSpecification>
+      <dataInputAssociation>
+        <sourceRef>x</sourceRef>
+        <targetRef>_2_subXInput</targetRef>
+      </dataInputAssociation>
+      <dataOutputAssociation>
+        <sourceRef>_2_subYOutput</sourceRef>
+        <targetRef>y</targetRef>
+      </dataOutputAssociation>
+    </callActivity>
+    <endEvent id="_3" name="End"/>
+
+    <sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2"/>
+    <sequenceFlow id="_2-_3" sourceRef="_2" targetRef="_3"/>
+  </process>
+
+</definitions>
diff --git 
a/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityNoWait.bpmn2
 
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityNoWait.bpmn2
new file mode 100644
index 00000000000..2c278515c99
--- /dev/null
+++ 
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityNoWait.bpmn2
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<definitions id="Definition"
+             targetNamespace="http://www.example.org/MinimalExample";
+             typeLanguage="http://www.java.com/javaTypes";
+             expressionLanguage="http://www.mvel.org/2.0";
+             xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL";
+             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+             xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL 
BPMN20.xsd"
+             xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI";
+             xmlns:dc="http://www.omg.org/spec/DD/20100524/DC";
+             xmlns:di="http://www.omg.org/spec/DD/20100524/DI";
+             xmlns:tns="http://www.jboss.org/drools";>
+
+  <itemDefinition id="_subprocessIdItem" structureRef="String"/>
+
+  <!-- waitForCompletion=false: parent completes without waiting for child -->
+  <process processType="Private" isExecutable="true"
+           id="DynamicCallActivityNoWait"
+           name="Dynamic Call Activity No Wait"
+           tns:packageName="org.jbpm.bpmn2.subprocess">
+
+    <property id="subprocessId" itemSubjectRef="_subprocessIdItem"/>
+
+    <startEvent id="_1" name="Start"/>
+    <callActivity id="_2" name="DynamicSubProcessNoWait"
+                  calledElement="#{subprocessId}"
+                  tns:waitForCompletion="false"
+                  tns:independent="true">
+    </callActivity>
+    <endEvent id="_3" name="End"/>
+
+    <sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2"/>
+    <sequenceFlow id="_2-_3" sourceRef="_2" targetRef="_3"/>
+  </process>
+
+</definitions>
diff --git 
a/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityScriptResolved.bpmn2
 
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityScriptResolved.bpmn2
new file mode 100644
index 00000000000..e932d0eea61
--- /dev/null
+++ 
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityScriptResolved.bpmn2
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<!--
+  Parent process where a script task computes the subprocess ID at runtime
+  and stores it in a variable. The callActivity then resolves #{subprocessId}
+  from that script-assigned value. Tests that variable resolution sees values
+  written during the running instance, not just start-time parameters.
+-->
+<definitions id="Definition"
+             targetNamespace="http://www.example.org/MinimalExample";
+             typeLanguage="http://www.java.com/javaTypes";
+             expressionLanguage="http://www.mvel.org/2.0";
+             xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL";
+             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+             xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL 
BPMN20.xsd"
+             xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI";
+             xmlns:dc="http://www.omg.org/spec/DD/20100524/DC";
+             xmlns:di="http://www.omg.org/spec/DD/20100524/DI";
+             xmlns:tns="http://www.jboss.org/drools";>
+
+  <itemDefinition id="_subprocessIdItem" structureRef="String"/>
+  <itemDefinition id="_yItem"            structureRef="String"/>
+
+  <process processType="Private" isExecutable="true"
+           id="DynamicCallActivityScriptResolved"
+           name="Dynamic Call Activity Script Resolved"
+           tns:version="1"
+           tns:packageName="org.jbpm.bpmn2.subprocess">
+
+    <!-- subprocessId starts null; the script task sets it before the 
callActivity -->
+    <property id="subprocessId" itemSubjectRef="_subprocessIdItem"/>
+    <property id="y"            itemSubjectRef="_yItem"/>
+
+    <startEvent id="_1" name="Start"/>
+
+    <!-- Script sets the subprocess ID programmatically -->
+    <scriptTask id="_2" name="ComputeSubprocessId">
+      <script>kcontext.setVariable("subprocessId", 
"CallActivitySubProcess");</script>
+    </scriptTask>
+
+    <callActivity id="_3" name="DynamicSubProcess" 
calledElement="#{subprocessId}"
+                  tns:waitForCompletion="true" tns:independent="false">
+      <ioSpecification>
+        <dataOutput id="_3_subYOutput" name="subY"/>
+        <outputSet>
+          <dataOutputRefs>_3_subYOutput</dataOutputRefs>
+        </outputSet>
+      </ioSpecification>
+      <dataOutputAssociation>
+        <sourceRef>_3_subYOutput</sourceRef>
+        <targetRef>y</targetRef>
+      </dataOutputAssociation>
+    </callActivity>
+
+    <endEvent id="_4" name="End"/>
+
+    <sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2"/>
+    <sequenceFlow id="_2-_3" sourceRef="_2" targetRef="_3"/>
+    <sequenceFlow id="_3-_4" sourceRef="_3" targetRef="_4"/>
+  </process>
+
+</definitions>
diff --git 
a/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityTwoSequential.bpmn2
 
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityTwoSequential.bpmn2
new file mode 100644
index 00000000000..88e0de03f71
--- /dev/null
+++ 
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityTwoSequential.bpmn2
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<!--
+  Parent process with two sequential callActivity nodes.
+  Both use the same variable expression #{subprocessId}, but a script task
+  between them changes the value so each invocation resolves to a different
+  child process. This tests that resolution is truly per-execution, not cached.
+-->
+<definitions id="Definition"
+             targetNamespace="http://www.example.org/MinimalExample";
+             typeLanguage="http://www.java.com/javaTypes";
+             expressionLanguage="http://www.mvel.org/2.0";
+             xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL";
+             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+             xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL 
BPMN20.xsd"
+             xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI";
+             xmlns:dc="http://www.omg.org/spec/DD/20100524/DC";
+             xmlns:di="http://www.omg.org/spec/DD/20100524/DI";
+             xmlns:tns="http://www.jboss.org/drools";>
+
+  <itemDefinition id="_subprocessIdItem" structureRef="String"/>
+
+  <process processType="Private" isExecutable="true"
+           id="DynamicCallActivityTwoSequential"
+           name="Dynamic Call Activity Two Sequential"
+           tns:version="1"
+           tns:packageName="org.jbpm.bpmn2.subprocess">
+
+    <property id="subprocessId" itemSubjectRef="_subprocessIdItem"/>
+
+    <startEvent id="_1" name="Start"/>
+
+    <!-- First call — resolves subprocessId to whatever was passed at start -->
+    <callActivity id="_2" name="FirstCall" calledElement="#{subprocessId}"
+                  tns:waitForCompletion="true" tns:independent="false"/>
+
+    <!-- Script switches the variable to the second child process -->
+    <scriptTask id="_3" name="SwitchSubprocess">
+      <script>kcontext.setVariable("subprocessId", 
"DynamicSubProcessB");</script>
+    </scriptTask>
+
+    <!-- Second call — resolves subprocessId to "DynamicSubProcessB" -->
+    <callActivity id="_4" name="SecondCall" calledElement="#{subprocessId}"
+                  tns:waitForCompletion="true" tns:independent="false"/>
+
+    <endEvent id="_5" name="End"/>
+
+    <sequenceFlow id="_1-_2" sourceRef="_1"  targetRef="_2"/>
+    <sequenceFlow id="_2-_3" sourceRef="_2"  targetRef="_3"/>
+    <sequenceFlow id="_3-_4" sourceRef="_3"  targetRef="_4"/>
+    <sequenceFlow id="_4-_5" sourceRef="_4"  targetRef="_5"/>
+  </process>
+
+</definitions>
diff --git 
a/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityUnknownProcess.bpmn2
 
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityUnknownProcess.bpmn2
new file mode 100644
index 00000000000..7d2dfdea56c
--- /dev/null
+++ 
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityUnknownProcess.bpmn2
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<definitions id="Definition"
+             targetNamespace="http://www.example.org/MinimalExample";
+             typeLanguage="http://www.java.com/javaTypes";
+             expressionLanguage="http://www.mvel.org/2.0";
+             xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL";
+             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+             xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL 
BPMN20.xsd"
+             xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI";
+             xmlns:dc="http://www.omg.org/spec/DD/20100524/DC";
+             xmlns:di="http://www.omg.org/spec/DD/20100524/DI";
+             xmlns:tns="http://www.jboss.org/drools";>
+
+  <itemDefinition id="_subprocessIdItem" structureRef="String"/>
+
+  <!-- calledElement resolves to a non-existent process ID -->
+  <process processType="Private" isExecutable="true"
+           id="DynamicCallActivityUnknownProcess"
+           name="Dynamic Call Activity Unknown Process"
+           tns:packageName="org.jbpm.bpmn2.subprocess">
+
+    <property id="subprocessId" itemSubjectRef="_subprocessIdItem"/>
+
+    <startEvent id="_1" name="Start"/>
+    <callActivity id="_2" name="DynamicSubProcessUnknown" 
calledElement="#{subprocessId}"/>
+    <endEvent id="_3" name="End"/>
+
+    <sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2"/>
+    <sequenceFlow id="_2-_3" sourceRef="_2" targetRef="_3"/>
+  </process>
+
+</definitions>
diff --git 
a/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityWithBoundaryError.bpmn2
 
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityWithBoundaryError.bpmn2
new file mode 100644
index 00000000000..0d757f162b6
--- /dev/null
+++ 
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityWithBoundaryError.bpmn2
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<!--
+  Parent process that calls a dynamically-resolved subprocess.
+  An Error Boundary Event (errorCode="subprocess-fault") is attached to the
+  callActivity. When the called subprocess ends with an error end event
+  carrying the same errorCode, the boundary event fires and the process
+  routes to the error-handling script and end event instead of the normal end.
+-->
+<bpmn2:definitions
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+    xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL";
+    xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI";
+    xmlns:dc="http://www.omg.org/spec/DD/20100524/DC";
+    xmlns:di="http://www.omg.org/spec/DD/20100524/DI";
+    xmlns:tns="http://www.jboss.org/drools";
+    xmlns="http://www.jboss.org/drools";
+    xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd 
http://www.jboss.org/drools drools.xsd"
+    id="Definition"
+    expressionLanguage="http://www.mvel.org/2.0";
+    targetNamespace="http://www.jboss.org/drools";
+    typeLanguage="http://www.java.com/javaTypes";>
+
+  <!-- Error thrown by DynamicSubProcessFaulty -->
+  <bpmn2:error id="_subprocessFaultError" errorCode="subprocess-fault" 
name="Subprocess Fault"/>
+
+  <bpmn2:itemDefinition id="_subprocessIdItem" structureRef="String"/>
+  <bpmn2:itemDefinition id="_resultItem"       structureRef="String"/>
+
+  <bpmn2:process id="DynamicCallActivityWithBoundaryError"
+                 tns:version="1"
+                 tns:packageName="org.jbpm.bpmn2.subprocess"
+                 name="Dynamic Call Activity With Boundary Error"
+                 isExecutable="true"
+                 processType="Private">
+
+    <bpmn2:property id="subprocessId" itemSubjectRef="_subprocessIdItem"/>
+    <!-- result is written by the error-handling script so the test can assert 
it -->
+    <bpmn2:property id="result"       itemSubjectRef="_resultItem"/>
+
+    <bpmn2:startEvent id="_1" name="Start">
+      <bpmn2:outgoing>_1-_2</bpmn2:outgoing>
+    </bpmn2:startEvent>
+
+    <bpmn2:callActivity id="_2" name="DynamicSubProcess"
+                        calledElement="#{subprocessId}"
+                        tns:waitForCompletion="true"
+                        tns:independent="false">
+      <bpmn2:incoming>_1-_2</bpmn2:incoming>
+      <bpmn2:outgoing>_2-_3</bpmn2:outgoing>
+    </bpmn2:callActivity>
+
+    <!-- normal completion end -->
+    <bpmn2:endEvent id="_3" name="NormalEnd">
+      <bpmn2:incoming>_2-_3</bpmn2:incoming>
+    </bpmn2:endEvent>
+
+    <!-- error boundary event: catches errors with errorCode 
"subprocess-fault" -->
+    <bpmn2:boundaryEvent id="_4" name="ErrorBoundary" attachedToRef="_2" 
cancelActivity="true">
+      <bpmn2:outgoing>_4-_5</bpmn2:outgoing>
+      <bpmn2:errorEventDefinition id="_4_err" 
errorRef="_subprocessFaultError"/>
+    </bpmn2:boundaryEvent>
+
+    <!-- script to record that the boundary event was triggered -->
+    <bpmn2:scriptTask id="_5" name="RecordError" 
scriptFormat="http://www.java.com/java";>
+      <bpmn2:incoming>_4-_5</bpmn2:incoming>
+      <bpmn2:outgoing>_5-_6</bpmn2:outgoing>
+      <bpmn2:script>kcontext.setVariable("result", 
"error-caught");</bpmn2:script>
+    </bpmn2:scriptTask>
+
+    <!-- error-handled end event -->
+    <bpmn2:endEvent id="_6" name="ErrorHandledEnd">
+      <bpmn2:incoming>_5-_6</bpmn2:incoming>
+    </bpmn2:endEvent>
+
+    <bpmn2:sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2"/>
+    <bpmn2:sequenceFlow id="_2-_3" sourceRef="_2" targetRef="_3"/>
+    <bpmn2:sequenceFlow id="_4-_5" sourceRef="_4" targetRef="_5"/>
+    <bpmn2:sequenceFlow id="_5-_6" sourceRef="_5" targetRef="_6"/>
+  </bpmn2:process>
+
+</bpmn2:definitions>
diff --git 
a/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicSubProcessA.bpmn2
 
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicSubProcessA.bpmn2
new file mode 100644
index 00000000000..330fdcca5b1
--- /dev/null
+++ 
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicSubProcessA.bpmn2
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<!-- Child process A — sets outcome="A" so the parent can verify which was 
called -->
+<definitions id="Definition"
+             targetNamespace="http://www.example.org/MinimalExample";
+             typeLanguage="http://www.java.com/javaTypes";
+             expressionLanguage="http://www.mvel.org/2.0";
+             xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL";
+             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+             xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL 
BPMN20.xsd"
+             xmlns:tns="http://www.jboss.org/drools";>
+
+  <process processType="Private" isExecutable="true"
+           id="DynamicSubProcessA"
+           name="Dynamic SubProcess A"
+           tns:version="1"
+           tns:packageName="org.jbpm.bpmn2.subprocess">
+
+    <startEvent id="_1" name="Start"/>
+    <scriptTask id="_2" name="MarkA">
+      <script>System.out.println("DynamicSubProcessA running");</script>
+    </scriptTask>
+    <endEvent id="_3" name="End">
+      <terminateEventDefinition/>
+    </endEvent>
+
+    <sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2"/>
+    <sequenceFlow id="_2-_3" sourceRef="_2" targetRef="_3"/>
+  </process>
+
+</definitions>
diff --git 
a/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicSubProcessB.bpmn2
 
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicSubProcessB.bpmn2
new file mode 100644
index 00000000000..cbec556f00d
--- /dev/null
+++ 
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicSubProcessB.bpmn2
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<!-- Child process B — sets outcome="B" so the parent can verify which was 
called -->
+<definitions id="Definition"
+             targetNamespace="http://www.example.org/MinimalExample";
+             typeLanguage="http://www.java.com/javaTypes";
+             expressionLanguage="http://www.mvel.org/2.0";
+             xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL";
+             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+             xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL 
BPMN20.xsd"
+             xmlns:tns="http://www.jboss.org/drools";>
+
+  <process processType="Private" isExecutable="true"
+           id="DynamicSubProcessB"
+           name="Dynamic SubProcess B"
+           tns:version="1"
+           tns:packageName="org.jbpm.bpmn2.subprocess">
+
+    <startEvent id="_1" name="Start"/>
+    <scriptTask id="_2" name="MarkB">
+      <script>System.out.println("DynamicSubProcessB running");</script>
+    </scriptTask>
+    <endEvent id="_3" name="End">
+      <terminateEventDefinition/>
+    </endEvent>
+
+    <sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2"/>
+    <sequenceFlow id="_2-_3" sourceRef="_2" targetRef="_3"/>
+  </process>
+
+</definitions>
diff --git 
a/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicSubProcessFaulty.bpmn2
 
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicSubProcessFaulty.bpmn2
new file mode 100644
index 00000000000..4d31770d708
--- /dev/null
+++ 
b/kogito-jbpm/jbpm-tests/src/test/bpmn/org/jbpm/bpmn2/subprocess/BPMN2-DynamicSubProcessFaulty.bpmn2
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<!--
+  Faulty subprocess that immediately ends with an error end event
+  (errorCode="subprocess-fault"). Used by the boundary error tests
+  to verify that a parent callActivity's Error Boundary Event fires
+  when the called subprocess propagates a fault.
+-->
+<bpmn2:definitions
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+    xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL";
+    xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI";
+    xmlns:dc="http://www.omg.org/spec/DD/20100524/DC";
+    xmlns:di="http://www.omg.org/spec/DD/20100524/DI";
+    xmlns:tns="http://www.jboss.org/drools";
+    xmlns="http://www.jboss.org/drools";
+    xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd 
http://www.jboss.org/drools drools.xsd"
+    id="Definition"
+    expressionLanguage="http://www.mvel.org/2.0";
+    targetNamespace="http://www.jboss.org/drools";
+    typeLanguage="http://www.java.com/javaTypes";>
+
+  <bpmn2:error id="_fault" errorCode="subprocess-fault" name="Subprocess 
Fault"/>
+
+  <bpmn2:process id="DynamicSubProcessFaulty"
+                 tns:version="1"
+                 tns:packageName="org.jbpm.bpmn2.subprocess"
+                 name="Dynamic SubProcess Faulty"
+                 isExecutable="true"
+                 processType="Private">
+
+    <bpmn2:startEvent id="_1" name="Start">
+      <bpmn2:outgoing>_1-_2</bpmn2:outgoing>
+    </bpmn2:startEvent>
+
+    <!-- Error end event immediately propagates "subprocess-fault" to parent 
-->
+    <bpmn2:endEvent id="_2" name="FaultEnd">
+      <bpmn2:incoming>_1-_2</bpmn2:incoming>
+      <bpmn2:errorEventDefinition id="_2_err" errorRef="_fault"/>
+    </bpmn2:endEvent>
+
+    <bpmn2:sequenceFlow id="_1-_2" sourceRef="_1" targetRef="_2"/>
+  </bpmn2:process>
+
+</bpmn2:definitions>
diff --git 
a/kogito-jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/DynamicSubProcessTest.java
 
b/kogito-jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/DynamicSubProcessTest.java
new file mode 100644
index 00000000000..fe675074bf0
--- /dev/null
+++ 
b/kogito-jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/DynamicSubProcessTest.java
@@ -0,0 +1,468 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jbpm.bpmn2;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.jbpm.process.instance.impl.ProcessInstanceImpl;
+import org.jbpm.workflow.instance.impl.WorkflowProcessInstanceImpl;
+import org.junit.jupiter.api.Test;
+import org.kie.api.event.process.ProcessCompletedEvent;
+import org.kie.api.event.process.ProcessStartedEvent;
+import org.kie.kogito.internal.process.runtime.KogitoProcessInstance;
+import org.kie.kogito.internal.process.runtime.KogitoWorkflowProcessInstance;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests dynamic subprocess resolution where a BPMN {@code callActivity}
+ * {@code calledElement} may contain an MVEL expression evaluated at runtime.
+ *
+ * Covers:
+ * <ul>
+ * <li>Static and dynamic subprocess IDs</li>
+ * <li>Simple and compound MVEL expressions</li>
+ * <li>I/O data mappings</li>
+ * <li>Asynchronous execution ({@code waitForCompletion=false})</li>
+ * <li>Root process metadata propagation</li>
+ * <li>Invalid process IDs</li>
+ * <li>Repeated invocations with different variables</li>
+ * </ul>
+ */
+public class DynamicSubProcessTest extends JbpmBpmn2TestCase {
+
+    @Test
+    public void testCallActivityWithStaticProcessId() throws Exception {
+        kruntime = createKogitoProcessRuntime(
+                "org/jbpm/bpmn2/subprocess/BPMN2-CallActivity.bpmn2",
+                
"org/jbpm/bpmn2/subprocess/BPMN2-CallActivitySubProcess.bpmn2");
+
+        Map<String, Object> params = new HashMap<>();
+        params.put("x", "oldValue");
+
+        KogitoProcessInstance processInstance = 
kruntime.startProcess("CallActivity", params);
+
+        assertProcessInstanceCompleted(processInstance);
+        assertThat(((KogitoWorkflowProcessInstance) 
processInstance).getVariable("y"))
+                .isEqualTo("new value");
+    }
+
+    @Test
+    public void testCallActivityWithSimpleVariableExpression() throws 
Exception {
+        kruntime = createKogitoProcessRuntime(
+                
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityByVariable.bpmn2",
+                
"org/jbpm/bpmn2/subprocess/BPMN2-CallActivitySubProcess.bpmn2");
+
+        Map<String, Object> params = new HashMap<>();
+        params.put("subprocessId", "CallActivitySubProcess");
+        params.put("x", "dynamicInput");
+
+        KogitoProcessInstance processInstance = kruntime.startProcess(
+                "DynamicCallActivityByVariable", params);
+
+        assertProcessInstanceCompleted(processInstance);
+        assertThat(((KogitoWorkflowProcessInstance) 
processInstance).getVariable("y"))
+                .isEqualTo("new value");
+    }
+
+    @Test
+    public void testCallActivityWithMvelCompoundExpression() throws Exception {
+        kruntime = createKogitoProcessRuntime(
+                
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityByExpression.bpmn2",
+                
"org/jbpm/bpmn2/subprocess/BPMN2-CallActivitySubProcess.bpmn2");
+
+        Map<String, Object> params = new HashMap<>();
+        params.put("prefix", "CallActivity");
+        params.put("x", "expressionInput");
+
+        KogitoProcessInstance processInstance = kruntime.startProcess(
+                "DynamicCallActivityByExpression", params);
+
+        assertProcessInstanceCompleted(processInstance);
+        assertThat(((KogitoWorkflowProcessInstance) 
processInstance).getVariable("y"))
+                .isEqualTo("new value");
+    }
+
+    @Test
+    public void testCallActivityDynamicWithIoMappings() throws Exception {
+        kruntime = createKogitoProcessRuntime(
+                
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityByVariable.bpmn2",
+                
"org/jbpm/bpmn2/subprocess/BPMN2-CallActivitySubProcess.bpmn2");
+
+        Map<String, Object> params = new HashMap<>();
+        params.put("subprocessId", "CallActivitySubProcess");
+        params.put("x", "myInputValue");
+
+        KogitoProcessInstance processInstance = kruntime.startProcess(
+                "DynamicCallActivityByVariable", params);
+
+        assertProcessInstanceCompleted(processInstance);
+        // Subprocess sets subY = "new value"; it must flow back to parent 
variable y
+        String y = (String) ((KogitoWorkflowProcessInstance) 
processInstance).getVariable("y");
+        assertThat(y).isEqualTo("new value");
+    }
+
+    @Test
+    public void testCallActivityDynamicNoWait() throws Exception {
+        kruntime = createKogitoProcessRuntime(
+                
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityNoWait.bpmn2",
+                
"org/jbpm/bpmn2/subprocess/BPMN2-CallActivitySubProcess.bpmn2");
+
+        final List<String> startedSubprocesses = new ArrayList<>();
+        kruntime.getProcessEventManager().addEventListener(
+                new org.kie.api.event.process.DefaultProcessEventListener() {
+                    @Override
+                    public void beforeProcessStarted(ProcessStartedEvent 
event) {
+                        if 
("CallActivitySubProcess".equals(event.getProcessInstance().getProcessId())) {
+                            startedSubprocesses.add(((KogitoProcessInstance) 
event.getProcessInstance()).getStringId());
+                        }
+                    }
+                });
+
+        Map<String, Object> params = new HashMap<>();
+        params.put("subprocessId", "CallActivitySubProcess");
+
+        KogitoProcessInstance processInstance = kruntime.startProcess(
+                "DynamicCallActivityNoWait", params);
+
+        // Parent must have completed already
+        assertProcessInstanceCompleted(processInstance);
+        // Child was spawned
+        assertThat(startedSubprocesses).hasSize(1);
+    }
+
+    @Test
+    public void testRootProcessMetadataPropagation() throws Exception {
+        kruntime = createKogitoProcessRuntime(
+                
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityByVariable.bpmn2",
+                
"org/jbpm/bpmn2/subprocess/BPMN2-CallActivitySubProcess.bpmn2");
+
+        final List<ProcessInstanceImpl> childInstances = new ArrayList<>();
+        kruntime.getProcessEventManager().addEventListener(
+                new org.kie.api.event.process.DefaultProcessEventListener() {
+                    @Override
+                    public void beforeProcessStarted(ProcessStartedEvent 
event) {
+                        if 
("CallActivitySubProcess".equals(event.getProcessInstance().getProcessId())) {
+                            childInstances.add((ProcessInstanceImpl) 
event.getProcessInstance());
+                        }
+                    }
+                });
+
+        Map<String, Object> params = new HashMap<>();
+        params.put("subprocessId", "CallActivitySubProcess");
+        params.put("x", "rootTest");
+
+        KogitoProcessInstance parentInstance = kruntime.startProcess(
+                "DynamicCallActivityByVariable", params);
+
+        assertProcessInstanceCompleted(parentInstance);
+        assertThat(childInstances).hasSize(1);
+
+        ProcessInstanceImpl child = childInstances.get(0);
+        // When the parent is the root, rootProcessId must equal the parent's 
processId
+        
assertThat(child.getRootProcessId()).isEqualTo("DynamicCallActivityByVariable");
+        // rootProcessInstanceId must equal the parent's instance id
+        
assertThat(child.getRootProcessInstanceId()).isEqualTo(parentInstance.getStringId());
+        // rootProcessVersion must equal the parent's declared version "1"
+        assertThat(child.getRootProcessVersion()).isEqualTo("1");
+    }
+
+    @Test
+    public void testCallActivityDynamicUnknownProcessEndsInError() throws 
Exception {
+        kruntime = createKogitoProcessRuntime(
+                
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityUnknownProcess.bpmn2");
+
+        Map<String, Object> params = new HashMap<>();
+        params.put("subprocessId", "NonExistentProcess");
+
+        KogitoProcessInstance processInstance =
+                kruntime.startProcess("DynamicCallActivityUnknownProcess", 
params);
+
+        // The runtime catches the IllegalArgumentException and marks the 
instance as ERROR
+        
assertThat(processInstance.getState()).isEqualTo(KogitoProcessInstance.STATE_ERROR);
+        assertThat(((WorkflowProcessInstanceImpl) 
processInstance).getErrorMessage())
+                .contains("NonExistentProcess");
+    }
+
+    @Test
+    public void testCallActivityDynamicNullSubprocessIdEndsInError() throws 
Exception {
+        kruntime = createKogitoProcessRuntime(
+                
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityUnknownProcess.bpmn2");
+
+        Map<String, Object> params = new HashMap<>();
+        params.put("subprocessId", null);
+
+        KogitoProcessInstance processInstance =
+                kruntime.startProcess("DynamicCallActivityUnknownProcess", 
params);
+
+        
assertThat(processInstance.getState()).isEqualTo(KogitoProcessInstance.STATE_ERROR);
+        assertThat(((WorkflowProcessInstanceImpl) 
processInstance).getErrorMessage())
+                .contains("Could not find process");
+    }
+
+    @Test
+    public void testCallActivityDynamicEmptySubprocessIdEndsInError() throws 
Exception {
+        kruntime = createKogitoProcessRuntime(
+                
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityUnknownProcess.bpmn2");
+
+        Map<String, Object> params = new HashMap<>();
+        params.put("subprocessId", "");
+
+        KogitoProcessInstance processInstance =
+                kruntime.startProcess("DynamicCallActivityUnknownProcess", 
params);
+
+        
assertThat(processInstance.getState()).isEqualTo(KogitoProcessInstance.STATE_ERROR);
+        assertThat(((WorkflowProcessInstanceImpl) 
processInstance).getErrorMessage())
+                .contains("Could not find process");
+    }
+
+    @Test
+    public void testCallActivityDynamicBlankSubprocessIdEndsInError() throws 
Exception {
+        kruntime = createKogitoProcessRuntime(
+                
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityUnknownProcess.bpmn2");
+
+        Map<String, Object> params = new HashMap<>();
+        params.put("subprocessId", "   ");
+
+        KogitoProcessInstance processInstance =
+                kruntime.startProcess("DynamicCallActivityUnknownProcess", 
params);
+
+        
assertThat(processInstance.getState()).isEqualTo(KogitoProcessInstance.STATE_ERROR);
+        assertThat(((WorkflowProcessInstanceImpl) 
processInstance).getErrorMessage())
+                .contains("Could not find process");
+    }
+
+    @Test
+    public void testCallActivityDynamicMissingVariableEndsInError() throws 
Exception {
+        kruntime = createKogitoProcessRuntime(
+                
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityMissingVariable.bpmn2");
+
+        KogitoProcessInstance processInstance =
+                kruntime.startProcess("DynamicCallActivityMissingVariable");
+
+        
assertThat(processInstance.getState()).isEqualTo(KogitoProcessInstance.STATE_ERROR);
+        assertThat(((WorkflowProcessInstanceImpl) 
processInstance).getErrorMessage())
+                .contains("Could not find process");
+    }
+
+    @Test
+    public void testDynamicSubprocessFaultPropagatesAsAbort() throws Exception 
{
+        kruntime = createKogitoProcessRuntime(
+                
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityUnknownProcess.bpmn2",
+                
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicSubProcessFaulty.bpmn2");
+
+        Map<String, Object> params = new HashMap<>();
+        params.put("subprocessId", "DynamicSubProcessFaulty");
+
+        KogitoProcessInstance processInstance =
+                kruntime.startProcess("DynamicCallActivityUnknownProcess", 
params);
+
+        // When the subprocess faults and the parent has no matching exception 
handler,
+        // the parent completes normally (fault is silently absorbed, not 
propagated).
+        // This is the default kruntime behaviour when abortParent=false (the 
default).
+        assertThat(processInstance.getState())
+                .as("Without a boundary event, parent should complete normally 
when child faults")
+                .isEqualTo(KogitoProcessInstance.STATE_COMPLETED);
+    }
+
+    @Test
+    public void testCallActivityWithScriptResolvedSubprocessId() throws 
Exception {
+        kruntime = createKogitoProcessRuntime(
+                
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityScriptResolved.bpmn2",
+                
"org/jbpm/bpmn2/subprocess/BPMN2-CallActivitySubProcess.bpmn2");
+
+        // No subprocessId in params — the script inside the process sets it
+        KogitoProcessInstance processInstance =
+                kruntime.startProcess("DynamicCallActivityScriptResolved");
+
+        assertProcessInstanceCompleted(processInstance);
+        // CallActivitySubProcess sets subY="new value"; mapped back to y
+        assertThat(((KogitoWorkflowProcessInstance) 
processInstance).getVariable("y"))
+                .isEqualTo("new value");
+    }
+
+    @Test
+    public void testTwoSequentialCallsDispatchToDifferentSubprocesses() throws 
Exception {
+        kruntime = createKogitoProcessRuntime(
+                
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityTwoSequential.bpmn2",
+                "org/jbpm/bpmn2/subprocess/BPMN2-DynamicSubProcessA.bpmn2",
+                "org/jbpm/bpmn2/subprocess/BPMN2-DynamicSubProcessB.bpmn2");
+
+        final List<String> childIds = new ArrayList<>();
+        kruntime.getProcessEventManager().addEventListener(
+                new org.kie.api.event.process.DefaultProcessEventListener() {
+                    @Override
+                    public void beforeProcessStarted(ProcessStartedEvent 
event) {
+                        String pid = event.getProcessInstance().getProcessId();
+                        if (!"DynamicCallActivityTwoSequential".equals(pid)) {
+                            childIds.add(pid);
+                        }
+                    }
+                });
+
+        Map<String, Object> params = new HashMap<>();
+        params.put("subprocessId", "DynamicSubProcessA"); // first call uses 
this
+        KogitoProcessInstance processInstance =
+                kruntime.startProcess("DynamicCallActivityTwoSequential", 
params);
+
+        assertProcessInstanceCompleted(processInstance);
+        // Two child processes must have been spawned in order
+        assertThat(childIds)
+                .hasSize(2)
+                .containsExactly("DynamicSubProcessA", "DynamicSubProcessB");
+    }
+
+    @Test
+    public void testChildProcessReachesStateCompleted() throws Exception {
+        kruntime = createKogitoProcessRuntime(
+                
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityByVariable.bpmn2",
+                
"org/jbpm/bpmn2/subprocess/BPMN2-CallActivitySubProcess.bpmn2");
+
+        final List<KogitoProcessInstance> completedChildren = new 
ArrayList<>();
+        kruntime.getProcessEventManager().addEventListener(
+                new org.kie.api.event.process.DefaultProcessEventListener() {
+                    @Override
+                    public void afterProcessCompleted(ProcessCompletedEvent 
event) {
+                        if ("CallActivitySubProcess".equals(
+                                event.getProcessInstance().getProcessId())) {
+                            completedChildren.add((KogitoProcessInstance) 
event.getProcessInstance());
+                        }
+                    }
+                });
+
+        Map<String, Object> params = new HashMap<>();
+        params.put("subprocessId", "CallActivitySubProcess");
+        params.put("x", "completionCheck");
+
+        KogitoProcessInstance parent = 
kruntime.startProcess("DynamicCallActivityByVariable", params);
+
+        assertProcessInstanceCompleted(parent);
+        assertThat(completedChildren).hasSize(1);
+        assertThat(completedChildren.get(0).getState())
+                .isEqualTo(KogitoProcessInstance.STATE_COMPLETED);
+    }
+
+    @Test
+    public void testConcurrentParentInstancesResolveIndependently() throws 
Exception {
+        // Use the no-wait parent (no I/O mappings) to avoid variable-schema
+        // mismatches with the simple child processes A and B.
+        kruntime = createKogitoProcessRuntime(
+                
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityNoWait.bpmn2",
+                "org/jbpm/bpmn2/subprocess/BPMN2-DynamicSubProcessA.bpmn2",
+                "org/jbpm/bpmn2/subprocess/BPMN2-DynamicSubProcessB.bpmn2");
+
+        // Pair each parent instance ID with the child it spawned
+        final Map<String, String> parentToChild = new HashMap<>();
+        kruntime.getProcessEventManager().addEventListener(
+                new org.kie.api.event.process.DefaultProcessEventListener() {
+                    @Override
+                    public void beforeProcessStarted(ProcessStartedEvent 
event) {
+                        KogitoProcessInstance child = (KogitoProcessInstance) 
event.getProcessInstance();
+                        String childPid = child.getProcessId();
+                        if (!childPid.equals("DynamicCallActivityNoWait")) {
+                            ProcessInstanceImpl pi = (ProcessInstanceImpl) 
child;
+                            String parentInstanceId = (String) 
pi.getMetaData().get("ParentProcessInstanceId");
+                            parentToChild.put(parentInstanceId, childPid);
+                        }
+                    }
+                });
+
+        Map<String, Object> paramsA = new HashMap<>();
+        paramsA.put("subprocessId", "DynamicSubProcessA");
+        KogitoProcessInstance piA = 
kruntime.startProcess("DynamicCallActivityNoWait", paramsA);
+
+        Map<String, Object> paramsB = new HashMap<>();
+        paramsB.put("subprocessId", "DynamicSubProcessB");
+        KogitoProcessInstance piB = 
kruntime.startProcess("DynamicCallActivityNoWait", paramsB);
+
+        // no-wait parent completes immediately
+        assertProcessInstanceCompleted(piA);
+        assertProcessInstanceCompleted(piB);
+
+        // Each parent must have dispatched to exactly its own child
+        
assertThat(parentToChild.get(piA.getStringId())).isEqualTo("DynamicSubProcessA");
+        
assertThat(parentToChild.get(piB.getStringId())).isEqualTo("DynamicSubProcessB");
+    }
+
+    @Test
+    public void testCallActivityDynamicDispatchedIndependentlyPerInstance() 
throws Exception {
+        kruntime = createKogitoProcessRuntime(
+                
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityByVariable.bpmn2",
+                
"org/jbpm/bpmn2/subprocess/BPMN2-CallActivitySubProcess.bpmn2");
+
+        final List<String> childProcessIds = new ArrayList<>();
+        kruntime.getProcessEventManager().addEventListener(
+                new org.kie.api.event.process.DefaultProcessEventListener() {
+                    @Override
+                    public void beforeProcessStarted(ProcessStartedEvent 
event) {
+                        if 
(!"DynamicCallActivityByVariable".equals(event.getProcessInstance().getProcessId()))
 {
+                            
childProcessIds.add(event.getProcessInstance().getProcessId());
+                        }
+                    }
+                });
+
+        // First invocation
+        Map<String, Object> params1 = new HashMap<>();
+        params1.put("subprocessId", "CallActivitySubProcess");
+        params1.put("x", "first");
+        KogitoProcessInstance pi1 = 
kruntime.startProcess("DynamicCallActivityByVariable", params1);
+        assertProcessInstanceCompleted(pi1);
+
+        // Second invocation (same subprocess, new instance – verifies 
stateless resolution)
+        Map<String, Object> params2 = new HashMap<>();
+        params2.put("subprocessId", "CallActivitySubProcess");
+        params2.put("x", "second");
+        KogitoProcessInstance pi2 = 
kruntime.startProcess("DynamicCallActivityByVariable", params2);
+        assertProcessInstanceCompleted(pi2);
+
+        // Both dispatches hit CallActivitySubProcess
+        assertThat(childProcessIds)
+                .hasSize(2)
+                .containsOnly("CallActivitySubProcess");
+    }
+
+    /**
+     * Verifies that a {@code calledElement} built from <em>multiple</em> 
{@code #{...}}
+     * tokens (e.g. {@code "#{prefix}#{suffix}"}) is fully resolved before the 
subprocess
+     * is looked up. This mirrors the behaviour supported in legacy 7.67.x
+     * {@code SubProcessNodeInstance}.
+     */
+    @Test
+    public void testCallActivityWithMultipleVariableExpressions() throws 
Exception {
+        kruntime = createKogitoProcessRuntime(
+                
"org/jbpm/bpmn2/subprocess/BPMN2-DynamicCallActivityMultiExpression.bpmn2",
+                
"org/jbpm/bpmn2/subprocess/BPMN2-CallActivitySubProcess.bpmn2");
+
+        Map<String, Object> params = new HashMap<>();
+        // "#{prefix}#{suffix}" resolves to "CallActivity" + "SubProcess" = 
"CallActivitySubProcess"
+        params.put("prefix", "CallActivity");
+        params.put("suffix", "SubProcess");
+        params.put("x", "multiExprInput");
+
+        KogitoProcessInstance processInstance = kruntime.startProcess(
+                "DynamicCallActivityMultiExpression", params);
+
+        assertProcessInstanceCompleted(processInstance);
+        assertThat(((KogitoWorkflowProcessInstance) 
processInstance).getVariable("y"))
+                .isEqualTo("new value");
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to