Skip to main content

Error Handling in Process Engine (Phase 5)

Phase 5 introduces comprehensive error handling capabilities to the BankLingo Process Engine, enabling processes to gracefully handle failures, validate data, and implement recovery strategies.

Overview​

Error handling in the process engine allows you to:

  • ✅ Throw errors from JavaScript (Script/Service/User tasks)
  • ✅ Catch errors with boundary events
  • ✅ Recover from failures with retry, fallback, and circuit breaker patterns
  • ✅ Track error history throughout process execution
  • ✅ Implement business validations with controlled error flows

Core Concepts​

1. BpmnError​

BpmnError is a special error type that triggers boundary error events in BPMN processes:

// Throw a BpmnError
throw new BpmnError(errorCode, errorMessage);

// Example
throw new BpmnError('VALIDATION_ERROR', 'Credit score below minimum threshold');

Key Characteristics:

  • Controlled: Errors are caught by boundary events, process doesn't crash
  • Traceable: Error code and message stored in context
  • Recoverable: Error handlers can implement recovery logic

2. Error Boundaries​

Boundary events catch errors thrown by tasks:

<bpmn:scriptTask id="ValidateData" name="Validate Data">
<bpmn:script>
if (!context.customerId) {
throw new BpmnError('MISSING_DATA', 'Customer ID is required');
}
</bpmn:script>
</bpmn:scriptTask>

<!-- Catch the error -->
<bpmn:boundaryEvent id="ValidationError"
attachedToRef="ValidateData"
cancelActivity="true">
<bpmn:errorEventDefinition errorRef="ValidationError" />
</bpmn:boundaryEvent>

<!-- Handle the error -->
<bpmn:scriptTask id="HandleError" name="Handle Error">
<bpmn:script>
var error = context._lastError;
console.log('Error caught:', error.errorCode, error.errorMessage);
</bpmn:script>
</bpmn:scriptTask>

<bpmn:error id="ValidationError" errorCode="VALIDATION_ERROR" />

Boundary Event Types:

  • Interrupting (cancelActivity="true"): Cancels the task and follows error path
  • Non-Interrupting (cancelActivity="false"): Keeps task running, handles error in parallel

3. Error Context​

When an error is thrown, the engine stores error information in the process context:

// Access last error
var lastError = context._lastError;
console.log('Error Code:', lastError.errorCode); // 'VALIDATION_ERROR'
console.log('Error Message:', lastError.errorMessage); // 'Credit score below minimum'
console.log('Error Timestamp:', lastError.timestamp); // ISO 8601 timestamp
console.log('Error Task:', lastError.taskId); // 'ValidateData'

// Access error history
var errorHistory = context._errorHistory || [];
console.log('Total errors:', errorHistory.length);

errorHistory.forEach(function(err) {
console.log('Previous error:', err.errorCode, '-', err.errorMessage);
});

Error Types​

Validation Errors​

Validate user input or data before processing:

// Multi-field validation
var errors = [];

if (!context.email || !context.email.includes('@')) {
errors.push('Valid email is required');
}

if (!context.phoneNumber || context.phoneNumber.length < 10) {
errors.push('Valid phone number is required');
}

if (context.loanAmount < 1000 || context.loanAmount > 500000) {
errors.push('Loan amount must be between $1,000 and $500,000');
}

if (errors.length > 0) {
throw new BpmnError('VALIDATION_ERROR', errors.join('; '));
}

Business Rule Violations​

Enforce business rules and policies:

// Check approval authority
if (context.loanAmount > context.approverMaxAmount) {
throw new BpmnError('INSUFFICIENT_AUTHORITY',
'Loan amount $' + context.loanAmount + ' exceeds your approval limit of $' +
context.approverMaxAmount);
}

// Check required documents
var requiredDocs = ['ID', 'PROOF_OF_INCOME', 'BANK_STATEMENT'];
var uploadedDocs = context.uploadedDocuments || [];
var missingDocs = requiredDocs.filter(d => !uploadedDocs.includes(d));

if (missingDocs.length > 0) {
throw new BpmnError('MISSING_DOCUMENTS',
'Cannot approve with missing documents: ' + missingDocs.join(', '));
}

External System Errors​

Handle failures from external APIs or services:

Code Removed

Implementation details removed for security.

Contact support for implementation guidance.

Data Errors​

Handle missing or invalid data:

// Check required fields
if (!context.customerId) {
throw new BpmnError('MISSING_DATA', 'Customer ID is required');
}

// Check data integrity
if (context.creditScore < 300 || context.creditScore > 850) {
throw new BpmnError('INVALID_DATA',
'Credit score ' + context.creditScore + ' is outside valid range (300-850)');
}

// Check data consistency
if (context.loanAmount > context.annualIncome * 5) {
throw new BpmnError('INCONSISTENT_DATA',
'Loan amount exceeds 5x annual income threshold');
}

Common Error Codes​

Error CodeCategoryUse Case
VALIDATION_ERRORValidationInput data validation failures
BUSINESS_RULE_VIOLATIONBusiness LogicBusiness rule/policy violations
MISSING_DATADataRequired data not found
INVALID_DATADataData outside valid range/format
INCONSISTENT_DATADataData integrity violations
AUTHENTICATION_ERRORSecurityAuthentication failures
AUTHORIZATION_ERRORSecurityPermission/access denied
INSUFFICIENT_AUTHORITYSecurityApproval authority exceeded
GATEWAY_TIMEOUTExternalExternal system timeout
EXTERNAL_SYSTEM_ERRORExternalExternal dependency failure
PAYMENT_DECLINEDBusinessPayment/transaction declined
DUPLICATE_ERRORDataDuplicate resource detected
RESOURCE_NOT_FOUNDDataResource doesn't exist
RATE_LIMIT_EXCEEDEDExternalAPI rate limit reached
CIRCUIT_BREAKER_OPENSystemCircuit breaker protection active

Error Handling Strategies​

1. Retry Pattern​

Automatically retry failed operations:

<bpmn:serviceTask id="CallAPI" name="Call External API">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="ConnectorKey" value="CallPartnerAPICommand"/>
</custom:properties>
</bpmn:extensionElements>
</bpmn:serviceTask>

<!-- Retryable error (non-interrupting) -->
<bpmn:boundaryEvent id="RetryError"
attachedToRef="CallAPI"
cancelActivity="false">
<bpmn:errorEventDefinition errorRef="RetryError" />
</bpmn:boundaryEvent>

<!-- Permanent failure (interrupting) -->
<bpmn:boundaryEvent id="FatalError"
attachedToRef="CallAPI"
cancelActivity="true">
<bpmn:errorEventDefinition errorRef="FatalError" />
</bpmn:boundaryEvent>

<!-- Retry logic -->
<bpmn:scriptTask id="CalculateRetry" name="Calculate Retry">
<bpmn:script>
var retryCount = (context.apiRetryCount || 0) + 1;
var maxRetries = 3;

if (retryCount > maxRetries) {
throw new BpmnError('API_RETRY_EXHAUSTED',
'Failed after ' + maxRetries + ' attempts');
}

context.apiRetryCount = retryCount;
context.retryDelayMs = Math.pow(2, retryCount) * 1000; // Exponential backoff

return { retryCount: retryCount };
</bpmn:script>
</bpmn:scriptTask>

<bpmn:error id="RetryError" errorCode="GATEWAY_TIMEOUT" />
<bpmn:error id="FatalError" errorCode="API_RETRY_EXHAUSTED" />

2. Fallback Pattern​

Use alternative service or cached data:

<bpmn:serviceTask id="PrimaryService" name="Primary Service">
<!-- Primary service call -->
</bpmn:serviceTask>

<!-- Primary failed -->
<bpmn:boundaryEvent id="PrimaryFailed" attachedToRef="PrimaryService">
<bpmn:errorEventDefinition/>
</bpmn:boundaryEvent>

<!-- Try fallback -->
<bpmn:serviceTask id="FallbackService" name="Fallback Service">
<!-- Fallback service call -->
</bpmn:serviceTask>

<!-- Fallback failed -->
<bpmn:boundaryEvent id="FallbackFailed" attachedToRef="FallbackService">
<bpmn:errorEventDefinition/>
</bpmn:boundaryEvent>

<!-- Use cached data -->
<bpmn:scriptTask id="UseCachedData" name="Use Cached Data">
<bpmn:script>
logger.warn('Both services failed, using cached data');
context.serviceResult = context.cachedServiceData;
context.dataSource = 'CACHE';
</bpmn:script>
</bpmn:scriptTask>

3. Circuit Breaker Pattern​

Protect system from cascading failures:

var serviceName = 'PaymentGateway';
var circuitKey = 'circuit_' + serviceName;
var circuitState = context[circuitKey + '_state'] || 'CLOSED';
var failureCount = context[circuitKey + '_failures'] || 0;
var threshold = 10;

if (circuitState === 'OPEN') {
var lastFailureTime = new Date(context[circuitKey + '_lastFailure']);
var now = new Date();
var resetTimeout = 300000; // 5 minutes

if (now - lastFailureTime > resetTimeout) {
context[circuitKey + '_state'] = 'HALF_OPEN';
} else {
throw new BpmnError('CIRCUIT_BREAKER_OPEN',
serviceName + ' circuit breaker is OPEN. Service unavailable.');
}
}

// Proceed with service call...

4. Validation Loop Pattern​

Let users correct validation errors:

<bpmn:userTask id="SubmitForm" name="Submit Form">
<bpmn:extensionElements>
<custom:properties>
<custom:property name="ServerScript"><![CDATA[
if (!formData.email || !formData.email.includes('@')) {
throw new BpmnError('VALIDATION_ERROR', 'Valid email is required');
}
]]></custom:property>
</custom:properties>
</bpmn:extensionElements>
</bpmn:userTask>

<!-- Validation error (non-interrupting) -->
<bpmn:boundaryEvent id="ValidationError"
attachedToRef="SubmitForm"
cancelActivity="false">
<bpmn:errorEventDefinition errorRef="ValidationError" />
</bpmn:boundaryEvent>

<!-- Notify user -->
<bpmn:scriptTask id="NotifyError" name="Notify User">
<bpmn:script>
var error = context._lastError;
BankLingo.ExecuteCommand('SendFormErrors', {
taskId: 'SubmitForm',
errors: error.errorMessage
});
</bpmn:script>
</bpmn:scriptTask>

<!-- Loop back to form -->
<bpmn:sequenceFlow sourceRef="NotifyError" targetRef="SubmitForm" />

Error Handling Best Practices​

✅ Do This​

// ✅ Use specific error codes
throw new BpmnError('CREDIT_SCORE_TOO_LOW', 'Credit score below 600');

// ✅ Include helpful error messages
throw new BpmnError('MISSING_DATA',
'Customer ID is required for credit check');

// ✅ Validate early
if (!context.customerId) {
throw new BpmnError('MISSING_DATA', 'Customer ID is required');
}
// Then perform expensive operation

// ✅ Track retry attempts
context.retryCount = (context.retryCount || 0) + 1;

// ✅ Log errors
console.log('Error occurred:', errorCode, '-', errorMessage);

// ✅ Use non-interrupting boundaries for recoverable errors
<bpmn:boundaryEvent cancelActivity="false">

❌ Don't Do This​

// ❌ Generic error codes
throw new BpmnError('ERROR', 'Something went wrong');

// ❌ No error message
throw new BpmnError('VALIDATION_ERROR', '');

// ❌ Silent failures
if (error) {
return null; // Should throw BpmnError
}

// ❌ Regular JavaScript errors
throw new Error('Validation failed'); // Won't trigger boundary events

// ❌ Infinite retries
while (true) {
try { /* ... */ } catch(e) { /* retry forever */ }
}

// ❌ No error boundaries
<!-- Missing boundary events - errors crash process -->

Error End Events​

Terminate process with an error:

<bpmn:scriptTask id="CriticalCheck" name="Critical Fraud Check">
<bpmn:script>
if (context.fraudScore > 90) {
throw new BpmnError('FRAUD_DETECTED',
'Critical fraud indicators. Score: ' + context.fraudScore);
}
</bpmn:script>
</bpmn:scriptTask>

<!-- Error end event terminates process -->
<bpmn:endEvent id="FraudDetectedEnd" name="Fraud Detected">
<bpmn:errorEventDefinition errorRef="FraudError" />
</bpmn:endEvent>

<bpmn:error id="FraudError" errorCode="FRAUD_DETECTED" />

Integration with Task Types​

Script Task​

Service Task​

User Task​


Features Used:

  • Phase 5: Error Handling

Status: ✅ Production Ready
Version: 2.0
Last Updated: January 2026