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:
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 Code | Category | Use Case |
|---|---|---|
VALIDATION_ERROR | Validation | Input data validation failures |
BUSINESS_RULE_VIOLATION | Business Logic | Business rule/policy violations |
MISSING_DATA | Data | Required data not found |
INVALID_DATA | Data | Data outside valid range/format |
INCONSISTENT_DATA | Data | Data integrity violations |
AUTHENTICATION_ERROR | Security | Authentication failures |
AUTHORIZATION_ERROR | Security | Permission/access denied |
INSUFFICIENT_AUTHORITY | Security | Approval authority exceeded |
GATEWAY_TIMEOUT | External | External system timeout |
EXTERNAL_SYSTEM_ERROR | External | External dependency failure |
PAYMENT_DECLINED | Business | Payment/transaction declined |
DUPLICATE_ERROR | Data | Duplicate resource detected |
RESOURCE_NOT_FOUND | Data | Resource doesn't exist |
RATE_LIMIT_EXCEEDED | External | API rate limit reached |
CIRCUIT_BREAKER_OPEN | System | Circuit 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
- Throw BpmnError from JavaScript
- Validate data, enforce business rules
- See Script Task Error Handling
Service Task
- Throw BpmnErrorException from C# commands
- Handle external API failures
- See Service Task Error Handling
User Task
- Validate form submissions in ServerScript
- Enforce business rules on user actions
- See User Task Error Handling
Related Documentation
- JavaScript Error Throwing - Complete BpmnError guide
- Boundary Error Events - Error boundary patterns
- Error Recovery Patterns - Advanced recovery strategies
- Script Task - Script error handling
- Service Task - Service error handling
- User Task - Form validation
Features Used:
- Phase 5: Error Handling
Status: ✅ Production Ready
Version: 2.0
Last Updated: January 2026