1. Why This Is Useful
When a new app version is deployed, some process instances may still be running on an older version.
This script migrates running instances from one version to another and handles missing target process definitions safely.
2. Where to Use in Joget
- Form Binder script for admin migration forms.
- Maintenance workflows used by platform administrators.
- Controlled production migration tasks after version deployment.
3. Inputs Required
The script reads these submitted form fields:
app_idfrom_versionto_version-
single_processId(available in script; optional strategy)
4. What the Script Does
- Reads running instances for the source app/version.
- Loads process definitions from the target version.
- Builds allowed target process definition IDs.
- Migrates each process with
processCopyFromInstanceIdwhen valid. - Aborts process if mapped target definition is missing.
- Stores migration count in
num_migrated.
5. Full Working Code
https://dev.to/exploringmylifeworks/process-migration-between-app-versions-in-joget-bok
import java.util.Collection;
import java.util.ArrayList;
import org.joget.commons.util.LogUtil;
import org.joget.apps.form.model.Element;
import org.joget.apps.form.model.FormData;
import org.joget.apps.form.model.FormRow;
import org.joget.apps.form.model.FormRowSet;
import org.joget.apps.form.service.FormUtil;
import org.joget.apps.form.model.FormStoreBinder;
import org.joget.plugin.base.PluginManager;
import org.joget.apps.app.model.AppDefinition;
import org.joget.apps.app.model.PackageDefinition;
import org.joget.apps.app.service.AppUtil;
import org.joget.workflow.model.WorkflowProcess;
import org.joget.workflow.model.service.WorkflowManager;
import org.joget.workflow.util.WorkflowUtil;
public FormRowSet execute(element, rows, formData) {
//check the rows is not empty before run it
if (rows != null && !rows.isEmpty()) {
WorkflowManager workflowManager = (WorkflowManager) AppUtil.getApplicationContext().getBean("workflowManager");
//Get the submitted data
FormRow row = rows.get(0);
String appId = row.getProperty("app_id");
String fromVersion = row.getProperty("from_version");
String toVersion = row.getProperty("to_version");
String single_processId = row.getProperty("single_processId");
Collection runningProcessList = workflowManager.getRunningProcessList(appId, null, null, fromVersion, null, null, 0, null);
//Collection runningProcessList = workflowManager.getRunningProcessList(appId, single_processId, null, fromVersion, null, null, 0, null);
Collection processes = workflowManager.getProcessList(appId, toVersion);
LogUtil.info("BeanShell", "Updating running processes for " + appId + " from " + fromVersion + " to " + toVersion);
if (!runningProcessList.isEmpty() && !processes.isEmpty()) {
Collection newProcessDefIds = new ArrayList();
for (WorkflowProcess process : processes) {
newProcessDefIds.add(process.getId());
}
for (WorkflowProcess process : runningProcessList) {
String processId = null;
try {
processId = process.getInstanceId();
String processDefId = process.getId();
processDefId = processDefId.replace("#" + fromVersion.toString() + "#", "#" + toVersion.toString() + "#");
if (newProcessDefIds.contains(processDefId)) {
workflowManager.processCopyFromInstanceId(processId, processDefId, true);
LogUtil.info("BeanShell", "Migrated process " + processId + ".");
} else {
workflowManager.processAbort(processId);
LogUtil.info("BeanShell", "Process Def ID " + processDefId + " does not exist. Aborted process " + processId + ".");
}
} catch (Exception e) {
LogUtil.error(getClass().getName(), e, "Error updating process " + processId);
}
}
row.setProperty("num_migrated", runningProcessList.size().toString());
LogUtil.info("BeanShell", "Completed updating running processes for " + appId + " from " + fromVersion + " to " + toVersion);
} else {
LogUtil.info("BeanShell", "No running processes to update for " + appId + " from " + fromVersion + " to " + toVersion);
}
//Reuse Workflow Form Binder to store data
PluginManager pluginManager = (PluginManager) AppUtil.getApplicationContext().getBean("pluginManager");
FormStoreBinder binder = (FormStoreBinder) pluginManager.getPlugin("org.joget.apps.form.lib.WorkflowFormBinder");
binder.store(element, rows, formData);
}
return rows;
}
return execute(element, rows, formData);
6. Quick Validation Checklist
- Confirm
from_versionandto_versionare correct. - Validate target version contains required process definitions.
- Test first with a small controlled process set.
- Verify
num_migratedand logs after execution.
7. 📘 Log Headings to Watch
Use these log headings so admins can quickly understand migration results:
-
Updating running processes for ...-> migration started. -
Migrated process ...-> one process migrated successfully. -
Process Def ID ... does not exist. Aborted process ...-> target mapping missing. -
Completed updating running processes for ...-> migration finished. -
No running processes to update for ...-> nothing matched for migration.
8. Security and Publishing Note
- Replace real app IDs, versions, and process IDs with placeholders in public posts.
- Avoid exposing internal release/versioning strategy details.
9. Final Note
This is a practical maintenance pattern for safe process migration during version upgrades, especially in active production environments.
Follow-up topic: Dry-run migration mode with preview and report output.
Top comments (2)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.