Azure Logic Apps Error Handling
Try/Catch Scopes and Retry Policies
Introduction
Robust error handling is essential for building reliable Logic Apps workflows. Without proper error handling, failures can cascade through your workflow, leaving messages unprocessed and systems in inconsistent states. Logic Apps provides Try/Catch scopes, retry policies, and run-after configuration to handle failures gracefully and maintain workflow reliability.
This comprehensive guide covers:
- Scope actions — Grouping actions for error handling
- Try/Catch pattern — Implementing error detection and handling
- Run after configuration — Controlling execution based on outcomes
- Retry policies — Handling transient failures automatically
- Error output access — Reading error details for logging/alerting
- Best practices — Building resilient workflows
Understanding Error Flow
Error Propagation in Workflows
┌─────────────────────────────────────────────────────────────────────┐
│ ERROR PROPAGATION FLOW │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ NORMAL EXECUTION │ │
│ │ │ │
│ │ Action 1 ──▶ Action 2 ──▶ Action 3 ──▶ Success │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ WITH ERROR HANDLING │ │
│ │ │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ Try │──▶── │ Success │ │ Catch │ │ │
│ │ │ Scope │ │ Path │ │ Scope │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ │ │ │ │ │ │
│ │ ▼ │ │ │ │
│ │ Action fails │ │ │ │
│ │ │ │ │ │ │
│ │ └────────────────┘────────────────┘ │ │
│ │ (Either path executes) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
Try/Catch Pattern Architecture
┌─────────────────────────────────────────────────────────────────────┐
│ TRY/CATCH ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ TRY SCOPE │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ [Action 1] ───▶ [Action 2] ───▶ [Action 3] │ │ │
│ │ │ │ │ │
│ │ │ If any action fails: │ │ │
│ │ │ - Workflow marked as "Failed" │ │ │
│ │ │ - Error details captured │ │ │
│ │ │ - Execution moves to Catch scope │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────┬────────────────────────────────┘ │
│ │ │
│ ┌───────────┴───────────┐ │
│ │ Run After Settings │ │
│ │ - has failed: true │ │
│ └───────────┬───────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ CATCH SCOPE │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ [Log Error] ───▶ [Send Alert] ───▶ [Clean Up] │ │ │
│ │ │ │ │ │
│ │ │ Execute error handling actions: │ │ │
│ │ │ - Access error details from workflow() │ │ │
│ │ │ - Send notifications │ │ │
│ │ │ - Perform cleanup │ │ │
│ │ │ - Retry if appropriate │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
Implementing Try/Catch
Step-by-Step Setup
{
"definition": {
"actions": {
"Try": {
"type": "Scope",
"actions": {
"Get_Order": {
"type": "ServiceBus",
"inputs": {
"method": "get",
"path": "/queues/orders/messages/dequeue"
}
},
"Process_Order": {
"type": "Http",
"inputs": {
"method": "POST",
"uri": "https://api.example.com/process",
"body": "@body('Get_Order')"
}
},
"Complete_Order": {
"type": "ServiceBus",
"inputs": {
"method": "post",
"path": "/queues/orders/messages/complete",
"body": {
"lockToken": "@body('Get_Order')?['LockToken']"
}
}
}
}
},
"Catch": {
"type": "Scope",
"runAfter": {
"Try": {
"failed": true
}
},
"actions": {
"Log_Error": {
"type": "Log",
"inputs": {
"message": "@workflow()['errors']"
}
},
"Send_Alert": {
"type": "SendEmail",
"inputs": {
"to": "ops@company.com",
"subject": "Workflow Failed: Order Processing",
"body": "@outputs('Try')"
}
}
}
}
}
}
}
Configure Run After
┌─────────────────────────────────────────────────────────────────────┐
│ RUN AFTER CONFIGURATION │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Catch Scope → Configure Run After │
│ ───────────────────────────────────── │
│ │
│ ☑ is successful (optional - handle normal completion) │
│ ☑ has failed (required - handle errors) │
│ ☑ has timed out (optional - handle timeouts) │
│ ☐ is skipped (optional - handle skipped actions) │
│ │
│ Common configurations: │
│ │
│ Catch on failure: │
│ ☑ has failed │
│ │
│ Catch on any non-success: │
│ ☐ is successful │
│ ☑ has failed │
│ ☑ has timed out │
│ │
└─────────────────────────────────────────────────────────────────────┘
Handle Different Error Types
Transient vs Permanent Errors
{
"Handle_transient_errors": {
"type": "Scope",
"actions": {
"Call_API": {
"type": "Http",
"inputs": { "uri": "https://api.example.com/data" },
"runAfter": {}
}
}
},
"Handle_failure": {
"type": "Scope",
"runAfter": {
"Handle_transient_errors": { "failed": true }
},
"actions": {
"Check_error_type": {
"type": "Condition",
"expression": "@contains(toLower(workflow().errors[0].message), 'timeout')",
"actions": {
"Retry_later": {
"type": "Http",
"inputs": {
"method": "POST",
"uri": "https://example.com/retry-queue",
"body": "@workflow().errors[0]"
}
}
},
"else": {
"actions": {
"Send_alert": {
"type": "SendEmail",
"inputs": {
"subject": "Permanent Failure",
"body": "@workflow().errors[0].message"
}
}
}
}
}
}
}
}
HTTP Error Handling
{
"Http_with_status_handling": {
"type": "Http",
"inputs": {
"method": "GET",
"uri": "https://api.example.com/data"
},
"runAfter": {},
"runtimeConfiguration": {
"conditions": [
{
"expression": "@equals(outputs('Http_Request')?['statusCode'], 404)",
"actions": {
"Handle_NotFound": { "type": "Log", "inputs": { "message": "Not found" } }
}
},
{
"expression": "@greaterOrEquals(outputs('Http_Request')?['statusCode'], 500)",
"actions": {
"Handle_ServerError": { "type": "Log", "inputs": { "message": "Server error" } }
}
}
]
}
}
}
Retry Policies
Configure Retry Options
{
"retry_policy_examples": {
"Fixed_retry": {
"type": "Http",
"retryPolicy": {
"type": "fixed",
"count": 3,
"interval": "PT5S"
},
"inputs": { "uri": "https://api.example.com/data" }
},
"Exponential_retry": {
"type": "Http",
"retryPolicy": {
"type": "exponential",
"count": 4,
"interval": "PT5S"
},
"inputs": { "uri": "https://api.example.com/data" }
},
"No_retry": {
"type": "Http",
"retryPolicy": {
"type": "none"
},
"inputs": { "uri": "https://api.example.com/data" }
}
}
}
Retry Policy Reference
┌─────────────────────────────────────────────────────────────────────┐
│ RETRY POLICY OPTIONS │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Type: fixed │
│ ───────────────── │
│ Retry at fixed intervals │
│ count: 1-90 (default: 4) │
│ interval: ISO 8601 format (default: PT5S = 5 seconds) │
│ Example: 5s, 5s, 5s, 5s │
│ │
│ Type: exponential │
│ ─────────────────── │
│ Retry with exponential backoff │
│ count: 1-90 (default: 4) │
│ interval: ISO 8601 format (default: PT5S = 5 seconds) │
│ Example: 5s, 10s, 20s, 40s (doubles each time) │
│ │
│ Type: none │
│ ──────────── │
│ No automatic retries - fail immediately │
│ Use for: Permanent failures, idempotent operations │
│ │
│ When to Retry: │
│ ───────────────── │
│ ✓ Network timeouts │
│ ✓ Service temporarily unavailable │
│ ✓ Rate limiting (429) │
│ ✓ Internal server errors (5xx) │
│ │
│ Don't Retry: │
│ ───────────── │
│ ✗ Authentication failures (401, 403) │
│ ✗ Validation errors (4xx except 429) │
│ ✗ Resource not found (404) │
│ ✗ Business logic errors │
│ │
└─────────────────────────────────────────────────────────────────────┘
Service Bus Retry Configuration
{
"ServiceBus_with_retry": {
"type": "ServiceBus",
"retryPolicy": {
"type": "exponential",
"count": 3,
"interval": "PT10S"
},
"inputs": {
"method": "post",
"path": "/queues/orders/messages"
}
}
}
Error Output and Logging
Access Error Details
{
"Access_error_info": {
"type": "Compose",
"inputs": {
"error_message": "@workflow().errors[0].message",
"error_code": "@workflow().errors[0].code",
"error_action": "@workflow().errors[0].actionName",
"error_timestamp": "@workflow().errors[0].timestamp",
"all_errors": "@workflow().errors"
}
}
}
Comprehensive Error Logging
{
"Log_error_details": {
"type": "Compose",
"inputs": {
"workflowName": "@workflow().name",
"workflowRunId": "@workflow().run.id",
"error": {
"message": "@workflow().errors[0].message",
"code": "@workflow().errors[0].code",
"action": "@workflow().errors[0].actionName",
"timestamp": "@workflow().errors[0].timestamp"
},
"context": {
"trigger": "@trigger().name",
"triggerInputs": "@trigger().inputs",
"retryCount": "@variables('retryCount')"
}
}
}
}
Send Detailed Alert
{
"Send_error_alert": {
"type": "SendEmail",
"inputs": {
"to": "ops@company.com",
"subject": "@concat('Workflow Error: ', workflow().name, ' - ', string(utcNow()))",
"body": "<html><body><h2>Error Details</h2><p><b>Workflow:</b> @{workflow().name}</p><p><b>Run ID:</b> @{workflow().run.id}</p><p><b>Error:</b> @{workflow().errors[0].message}</p><p><b>Action:</b> @{workflow().errors[0].actionName}</p><p><b>Time:</b> @{workflow().errors[0].timestamp}</p></body></html>"
}
}
}
Real-World Patterns
Complete Workflow with Error Handling
{
"Order_processing_workflow": {
"triggers": {
"When_order_received": {
"type": "ServiceBusTrigger",
"inputs": { "queueName": "orders-queue" }
}
},
"actions": {
"Try": {
"type": "Scope",
"actions": {
"Parse_Order": {
"type": "ParseJson",
"inputs": { "content": "@triggerBody()" }
},
"Validate_Order": {
"type": "Condition",
"expression": "@not(empty(body('Parse_Order')?['orderId']))",
"actions": {},
"else": {
"actions": {
"Throw_validation_error": {
"type": "Terminate",
"inputs": { "statusCode": "BadRequest", "message": "Invalid order" }
}
}
}
},
"Process_Order": {
"type": "Http",
"inputs": {
"method": "POST",
"uri": "https://api.example.com/orders",
"body": "@body('Parse_Order')"
},
"retryPolicy": { "type": "exponential", "count": 3, "interval": "PT5S" }
},
"Complete": {
"type": "ServiceBus",
"inputs": {
"method": "post",
"path": "/queues/orders/messages/complete",
"body": { "lockToken": "@triggerBody()?['LockToken']" }
}
}
}
},
"Catch": {
"type": "Scope",
"runAfter": {
"Try": { "failed": true }
},
"actions": {
"Log_error": {
"type": "Compose",
"inputs": {
"error": "@workflow().errors[0]",
"order": "@triggerBody()",
"timestamp": "@utcNow()"
}
},
"Save_to_error_queue": {
"type": "ServiceBus",
"inputs": {
"method": "post",
"path": "/queues/error-orders/messages",
"body": "@outputs('Log_error')"
}
},
"Send_alert": {
"type": "SendEmail",
"inputs": {
"to": "ops@company.com",
"subject": "Order Processing Failed",
"body": "@outputs('Log_error')"
}
}
}
}
}
}
}
Nested Error Handling
{
"Nested_try_catch": {
"actions": {
"Outer_Try": {
"type": "Scope",
"actions": {
"Inner_Try": {
"type": "Scope",
"actions": {
"Risky_Operation": { "type": "Http" }
},
"runAfter": {}
},
"Success_Action": {
"type": "Log",
"inputs": { "message": "Operation succeeded" },
"runAfter": { "Inner_Try": ["Succeeded"] }
}
}
},
"Outer_Catch": {
"type": "Scope",
"runAfter": {
"Outer_Try": { "failed": true }
},
"actions": {
"Handle_error": { "type": "SendEmail" }
}
}
}
}
}
Best Practices
Error Handling Checklist
| Practice | Description |
|---|---|
| Always use Try/Catch | Wrap critical operations in scopes |
| Configure run after | Set proper failure conditions |
| Add retry policies | Handle transient failures |
| Log error details | Capture for debugging |
| Send notifications | Alert on failures |
| Clean up resources | Release locks, close connections |
Design Patterns
┌─────────────────────────────────────────────────────────────────────┐
│ ERROR HANDLING BEST PRACTICES │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. SCOPE CRITICAL OPERATIONS │
│ └─ Group related actions in single scope │
│ └─ Don't try/catch individual small actions │
│ │
│ 2. HANDLE SPECIFIC ERRORS │
│ └─ Check error type before taking action │
│ └─ Retry transient, alert on permanent │
│ │
│ 3. LOG EVERYTHING │
│ └─ Save error details to storage for analysis │
│ └─ Include context: workflow name, run ID, inputs │
│ │
│ 4. NOTIFY APPROPRIATELY │
│ └─ Critical failures → immediate alert │
│ └─ Non-critical → batched notifications │
│ │
│ 5. CLEAN UP RESOURCES │
│ └─ Always complete or dead-letter Service Bus messages │
│ └─ Release any locks or temporary resources │
│ │
└─────────────────────────────────────────────────────────────────────┘
Related Topics
- Expressions — Dynamic content and logic
- Parallel Execution — Concurrent processing
- Service Bus — Service Bus integration
Azure Integration Hub - Intermediate Level