Overview
When a Joget BeanShell script runs once, a small resource leak may not look serious. When the same script runs inside workflow tools, schedulers, post-processing scripts, or high-traffic forms, unclosed resources can slowly exhaust database connections, mail sessions, streams, memory, or thread context.
This article focuses on practical leak prevention for Joget developers writing BeanShell scripts.
How It Works
Most issues are not classic Java heap leaks. They are usually resource leaks:
- Database connections not closed.
- ResultSet or PreparedStatement objects left open.
- Mail folders, stores, files, streams, or HTTP responses not closed.
- Workflow thread user changed and not restored.
- Large result sets loaded into memory without paging.
- Service/plugin lookups repeated inside loops.
The fix is to keep resource scope small and always clean up in inally.
Where to Use in Joget
Use this pattern in Workflow Builder, Form Builder, Userview actions, schedulers, BeanShell tools, validators, and post-processing scripts.
Full Code
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import javax.sql.DataSource;
import org.joget.apps.app.service.AppUtil;
import org.joget.commons.util.LogUtil;
import org.joget.workflow.model.service.WorkflowUserManager;
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
String originalUser = null;
try {
WorkflowUserManager workflowUserManager = (WorkflowUserManager) AppUtil.getApplicationContext().getBean("workflowUserManager");
originalUser = workflowUserManager.getCurrentUsername();
// Use a service account only when the script really needs elevated access.
workflowUserManager.setCurrentThreadUser("system");
DataSource ds = (DataSource) AppUtil.getApplicationContext().getBean("setupDataSource");
con = ds.getConnection();
ps = con.prepareStatement(
"select id, c_request_no, c_status " +
"from app_fd_request " +
"where c_status = ?"
);
ps.setString(1, "Pending");
rs = ps.executeQuery();
while (rs.next()) {
String requestId = rs.getString("id");
String requestNo = rs.getString("c_request_no");
String status = rs.getString("c_status");
LogUtil.info("cleanup-example", requestId + " | " + requestNo + " | " + status);
}
} catch (Exception e) {
LogUtil.error("cleanup-example", e, "Error while running BeanShell script.");
} finally {
try { if (rs != null) rs.close(); } catch (Exception e) { LogUtil.warn("cleanup-example", "Could not close ResultSet."); }
try { if (ps != null) ps.close(); } catch (Exception e) { LogUtil.warn("cleanup-example", "Could not close PreparedStatement."); }
try { if (con != null) con.close(); } catch (Exception e) { LogUtil.warn("cleanup-example", "Could not close Connection."); }
try {
WorkflowUserManager workflowUserManager = (WorkflowUserManager) AppUtil.getApplicationContext().getBean("workflowUserManager");
workflowUserManager.setCurrentThreadUser(originalUser);
} catch (Exception e) {
LogUtil.warn("cleanup-example", "Could not restore workflow user context.");
}
}
Example Use Cases
- Workflow tools that read or update many form rows.
- Validators that query related records.
- Email polling scripts.
- Scheduled cleanup jobs.
- Integration scripts that call HTTP APIs.
- Approval-history or audit-log insert scripts.
Customization Tips
- Prefer Joget's setupDataSource over hard-coded JDBC credentials.
- Use PreparedStatement instead of string-concatenated SQL.
- Close ResultSet, PreparedStatement, and Connection in reverse order.
- Restore WorkflowUserManager current thread user in inally.
- Avoid storing large lists in memory when the script can process rows one at a time.
- For big jobs, process records in batches and log progress.
- Do not keep static collections or cached row data inside BeanShell scripts unless you fully understand the lifecycle.
Key Benefits
- Reduces database connection exhaustion.
- Keeps workflow tools more stable under load.
- Makes scripts easier to debug when errors happen.
- Prevents user-context leakage between script executions.
- Helps avoid unnecessary heap growth from large in-memory objects.
Security Note
Memory safety and security often overlap. Avoid hard-coded database credentials, tokens, URLs, and private table names in scripts that may be shared. Keep examples generic and use platform-managed configuration wherever possible.
Final Thoughts
Increasing heap memory can hide a problem for a while, but it does not fix leaking resources. In Joget BeanShell scripts, the best habit is simple: open late, close early, and always clean up in inally.







