Error Handling Best Practices
Implement robust error handling that helps users and developers.
A Simple Analogy
Good error handling is like a helpful flight attendant. When turbulence hits (error), they explain what's happening, reassure passengers, and describe next steps. Bad error handling leaves everyone confused. Users need context and clarity, not cryptic stack traces.
What Is Effective Error Handling?
Error handling catches problems gracefully, communicates issues clearly to users, and provides developers with debugging information. It prevents cascading failures and improves user experience.
Why Error Handling Matters
- User experience: Clear messages vs. cryptic errors
- Debugging: Logs reveal problem root causes
- Resilience: Graceful degradation instead of crashes
- Trust: Users know you're handling issues
- Compliance: Security and audit logging
Error Types
| Type | Example | Handling | |------|---------|----------| | Validation | Invalid email format | Show user-friendly message | | Not Found | User ID doesn't exist | Return 404, inform user | | Permission | User lacks access | Return 403, inform user | | Server | Database connection failed | Log, return generic message | | Timeout | Request took too long | Retry or fail gracefully |
C# Error Handling Pattern
public class UserService
{
private readonly ILogger<UserService> _logger;
public async Task<Result<UserDto>> GetUserAsync(int id)
{
try
{
if (id <= 0)
{
_logger.LogWarning("Invalid user ID requested: {Id}", id);
return Result<UserDto>.Failure("Invalid user ID");
}
var user = await _repository.GetUserAsync(id);
if (user == null)
{
_logger.LogInformation("User not found: {Id}", id);
return Result<UserDto>.Failure("User not found");
}
return Result<UserDto>.Success(_mapper.Map<UserDto>(user));
}
catch (Exception ex)
{
_logger.LogError(ex, "Error fetching user {Id}", id);
return Result<UserDto>.Failure("An unexpected error occurred");
}
}
}
// Result wrapper for consistency
public class Result<T>
{
public bool IsSuccess { get; set; }
public T Data { get; set; }
public string ErrorMessage { get; set; }
public static Result<T> Success(T data) =>
new() { IsSuccess = true, Data = data };
public static Result<T> Failure(string error) =>
new() { IsSuccess = false, ErrorMessage = error };
}
REST API Error Response
{
"status": 400,
"code": "VALIDATION_ERROR",
"message": "Email format is invalid",
"details": [
{
"field": "email",
"issue": "Must be valid email"
}
],
"timestamp": "2025-02-20T10:30:00Z",
"traceId": "0HN1V2DCVJDR2:00000001"
}
Logging Best Practices
// Good: Contextual information
_logger.LogError(ex, "Failed to process payment for order {OrderId}. " +
"Amount: {Amount}, Customer: {CustomerId}",
orderId, amount, customerId);
// Bad: No context
_logger.LogError(ex, "Payment failed");
// Structure for analysis
_logger.LogWarning("High latency detected for user {UserId}: {ElapsedMs}ms",
userId, stopwatch.ElapsedMilliseconds);
Practical Example
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
[HttpPost]
public async Task<IActionResult> CreateOrder([FromBody] CreateOrderDto dto)
{
// Validation
if (!ModelState.IsValid)
return BadRequest(new {
error = "Invalid order data",
details = ModelState.Values.SelectMany(v => v.Errors)
});
try
{
var order = await _orderService.CreateAsync(dto);
return CreatedAtAction(nameof(GetOrder),
new { id = order.Id }, order);
}
catch (InsufficientStockException ex)
{
_logger.LogWarning(ex, "Stock unavailable for order creation");
return BadRequest(new { error = "Item out of stock" });
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error creating order");
return StatusCode(500, new {
error = "An unexpected error occurred"
});
}
}
}
Related Concepts to Explore
- Exception hierarchy and custom exceptions
- Global exception handlers (middleware)
- Structured logging and correlation IDs
- Health checks and circuit breakers
- Monitoring and alerting
Summary
Effective error handling combines clear user messages, detailed logging, and graceful degradation. This builds user trust, enables efficient debugging, and improves overall application resilience.