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