error-handling-patterns
Master error handling patterns across languages including exceptions, Result types, error propagation, and graceful degradation to build resilient applications. Use when implementing error handling, designing APIs, or improving application reliability.
When & Why to Use This Skill
This Claude skill provides a comprehensive framework for mastering error handling patterns to build highly resilient and fault-tolerant applications. It covers essential strategies including structured exception hierarchies, functional Result types, and advanced resilience policies like Retry, Circuit Breaker, and Graceful Degradation, with specific implementations for .NET and TypeScript environments.
Use Cases
- Designing resilient microservices that handle transient network failures and service outages using Polly's Retry and Circuit Breaker policies.
- Implementing type-safe error handling in TypeScript projects using the Result pattern to eliminate unhandled runtime exceptions and improve code clarity.
- Developing standardized exception hierarchies in .NET to provide clear, actionable feedback for both system errors and business logic validations.
- Configuring automated retry logic with exponential backoff and jitter for database operations to handle transient connection issues and deadlocks.
- Implementing graceful degradation strategies to ensure core application functionality remains available even when external dependencies or caches fail.
| name | error-handling-patterns |
|---|---|
| description | "Master error handling patterns across languages including exceptions, Result types, error propagation, and graceful degradation to build resilient applications. Use when implementing error handling, designing APIs, or improving application reliability." |
| layer | 1 |
| tech_stack | [dotnet, csharp, typescript] |
| topics | [exceptions, result-type, polly, retry, circuit-breaker, graceful-degradation] |
| depends_on | [] |
| complements | [dotnet-async-patterns] |
| keywords | [Exception, Result, Polly, Retry, CircuitBreaker, try-catch, BusinessException] |
Error Handling Patterns
Build resilient applications with robust error handling strategies.
Error Handling Philosophies
| Approach | Use When |
|---|---|
| Exceptions | Unexpected errors, exceptional conditions |
| Result Types | Expected errors, validation failures |
| Error Codes | C-style APIs, legacy integration |
.NET Exception Patterns
Custom Exception Hierarchy
public class ApplicationException : Exception
{
public string Code { get; }
public ApplicationException(string message, string code) : base(message)
{
Code = code;
}
}
public class ValidationException : ApplicationException
{
public ValidationException(string message)
: base(message, "VALIDATION_ERROR") { }
}
public class NotFoundException : ApplicationException
{
public NotFoundException(string resource, Guid id)
: base($"{resource} not found: {id}", "NOT_FOUND") { }
}
ABP BusinessException
// Use ABP's BusinessException for domain errors
throw new BusinessException(
code: ClinicManagementSystemDomainErrorCodes.PatientNotFound,
message: "Patient not found")
.WithData("PatientId", patientId);
Result Type Pattern
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E };
function Ok<T>(value: T): Result<T, never> {
return { ok: true, value };
}
function Err<E>(error: E): Result<never, E> {
return { ok: false, error };
}
// Usage
function parseJSON<T>(json: string): Result<T, SyntaxError> {
try {
return Ok(JSON.parse(json) as T);
} catch (error) {
return Err(error as SyntaxError);
}
}
.NET Resilience with Polly
HTTP Retry with Exponential Backoff
public IAsyncPolicy<HttpResponseMessage> BuildHttpRetryPolicy(int retryCount = 3)
{
return HttpPolicyExtensions
.HandleTransientHttpError()
.Or<TimeoutException>()
.WaitAndRetryAsync(
retryCount: retryCount,
sleepDurationProvider: retryAttempt =>
{
var exponentialDelay = TimeSpan.FromSeconds(Math.Pow(2, retryAttempt));
var jitter = TimeSpan.FromMilliseconds(Random.Shared.Next(0, 1000));
return exponentialDelay + jitter;
},
onRetryAsync: async (outcome, timespan, retryAttempt, context) =>
{
_logger.LogWarning(
"[Retry {Attempt}/{Total}] Waiting: {Delay:F2}s",
retryAttempt, retryCount, timespan.TotalSeconds);
});
}
Database Retry for Transient Errors
public IAsyncPolicy BuildDatabaseRetryPolicy(int retryCount = 3)
{
return Policy
.Handle<DbUpdateConcurrencyException>()
.Or<DbUpdateException>(ex =>
ex.InnerException is NpgsqlException npgsqlEx &&
IsTransientPostgresException(npgsqlEx))
.WaitAndRetryAsync(
retryCount: retryCount,
sleepDurationProvider: retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
}
private static bool IsTransientPostgresException(NpgsqlException ex)
{
var transientCodes = new[] { "40001", "40P01", "55P03", "57014", "53300", "08000" };
return transientCodes.Contains(ex.SqlState);
}
Combined Policy (Retry + Circuit Breaker + Timeout)
public IAsyncPolicy<HttpResponseMessage> BuildResilientPolicy()
{
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(30));
var retry = HttpPolicyExtensions
.HandleTransientHttpError()
.WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)));
var circuitBreaker = HttpPolicyExtensions
.HandleTransientHttpError()
.CircuitBreakerAsync(
handledEventsAllowedBeforeBreaking: 5,
durationOfBreak: TimeSpan.FromSeconds(30));
return Policy.WrapAsync(timeout, retry, circuitBreaker);
}
DI Registration
services.AddHttpClient<IMyApiClient, MyApiClient>()
.AddPolicyHandler((provider, _) =>
{
var retryService = provider.GetRequiredService<IRetryPolicyService>();
return retryService.BuildHttpRetryPolicy();
});
Circuit Breaker Pattern
States:
CLOSED → Normal operation, tracking failures
OPEN → Failing, reject all requests
HALF_OPEN → Testing recovery with limited requests
Flow:
CLOSED --[failure threshold]--> OPEN
OPEN --[timeout]--> HALF_OPEN
HALF_OPEN --[success]--> CLOSED
HALF_OPEN --[failure]--> OPEN
Graceful Degradation
// Polly Fallback Policy
public IAsyncPolicy<T> BuildFallbackPolicy<T>(Func<Task<T>> fallbackAction)
{
return Policy<T>
.Handle<Exception>()
.FallbackAsync(
fallbackAction: async (context, cancellationToken) =>
{
_logger.LogWarning("Primary operation failed, using fallback");
return await fallbackAction();
},
onFallbackAsync: async (exception, context) =>
{
_logger.LogError(exception.Exception, "Fallback triggered");
});
}
// Usage
var fallbackPolicy = BuildFallbackPolicy(() => FetchFromDatabaseAsync(userId));
var profile = await fallbackPolicy.ExecuteAsync(() => FetchFromCacheAsync(userId));
When to Retry
✅ Retry for:
- HTTP API calls (transient network errors)
- Database operations (deadlocks, connection timeouts)
- External service integrations
- File I/O operations
❌ Don't retry:
- Authentication failures
- Validation errors
- Business logic errors
- Non-idempotent operations without safeguards
Best Practices
- Fail Fast - Validate input early
- Preserve Context - Include stack traces, metadata
- Meaningful Messages - Explain what and how to fix
- Log Appropriately - Error = log, expected = don't spam
- Handle at Right Level - Catch where you can meaningfully handle
- Clean Up Resources - Use try-finally, using statements
- Don't Swallow Errors - Log or re-throw, don't ignore
- Type-Safe Errors - Use typed errors when possible
Detailed References
For comprehensive patterns, see: