Unhandled exceptions are inevitable in production applications. Database connections fail, external APIs become unavailable, unexpected null values appear, and business rules may throw custom exceptions. Without centralized exception handling, these errors often produce inconsistent responses, expose sensitive implementation details, and make troubleshooting difficult.
ASP.NET Core provides built-in middleware for handling exceptions globally, allowing applications to return consistent error responses while logging failures for diagnostics. A centralized error-handling strategy improves maintainability, enhances API usability, and prevents internal details from being exposed to clients.
Rather than wrapping every controller action in try-catch blocks, this article explains how to implement production-ready global exception handling in ASP.NET Core.
Note: Error responses should provide enough information for API consumers to understand what went wrong without exposing stack traces, connection strings, or other sensitive implementation details.
Why Global Exception Handling Matters
Without centralized exception handling, applications often suffer from:
A single exception handling pipeline keeps error handling consistent across the application.
Common Exception Types
Production applications frequently encounter:
Validation exceptions
Authentication failures
Authorization failures
Database exceptions
External API failures
File system errors
Timeout exceptions
Business rule violations
Different exceptions should return different HTTP status codes while following the same response format.
Exception Handling Flow
flowchart LR
A[Client Request]
B[ASP.NET Core Middleware]
C[Controller / Service]
D{Exception?}
E[Global Exception Handler]
F[ProblemDetails Response]
A --> B
B --> C
C --> D
D -->|No| A
D -->|Yes| E
E --> F
F --> A
Every unhandled exception flows through the global exception handler before a response is returned to the client.
Using the Built-in Exception Handler
Configure the exception handling middleware.
var app = builder.Build();
app.UseExceptionHandler("/error");
app.MapControllers();
app.Run();
This middleware intercepts unhandled exceptions before they reach the client.
Creating an Error Endpoint
Create a centralized endpoint for handling exceptions.
[ApiExplorerSettings(IgnoreApi = true)]
[Route("/error")]
public class ErrorController : ControllerBase
{
public IActionResult HandleError()
{
return Problem(
title: "An unexpected error occurred.",
statusCode: 500);
}
}
Returning a standardized response makes client-side error handling much simpler.
Using ProblemDetails
ASP.NET Core supports the RFC 7807 Problem Details format.
Example response:
{
"type": "about:blank",
"title": "Resource not found.",
"status": 404,
"detail": "The requested product does not exist."
}
Using ProblemDetails creates consistent error responses across the API.
Handling Custom Exceptions
Applications often define business-specific exceptions.
public class ProductNotFoundException
: Exception
{
public ProductNotFoundException(int id)
: base($"Product {id} was not found.")
{
}
}
Custom exceptions make application logic easier to understand and maintain.
Mapping Exceptions to Status Codes
Different exception types should produce appropriate HTTP responses.
| Exception | HTTP Status |
|---|
| ValidationException | 400 Bad Request |
| UnauthorizedAccessException | 401 Unauthorized |
| ProductNotFoundException | 404 Not Found |
| ConflictException | 409 Conflict |
| TimeoutException | 408 Request Timeout |
| Exception | 500 Internal Server Error |
Returning meaningful status codes improves API usability and debugging.
Logging Exceptions
Always log unexpected exceptions.
try
{
await service.ProcessAsync();
}
catch (Exception ex)
{
logger.LogError(
ex,
"Unexpected error while processing request.");
throw;
}
Structured logging makes production troubleshooting significantly easier.
Returning Validation Errors
Validation failures should return a 400 Bad Request.
if (!ModelState.IsValid)
{
return ValidationProblem(ModelState);
}
This provides clients with detailed validation information without exposing internal implementation details.
Common Production Mistakes
| Problem | Root Cause |
|---|
| Stack traces returned to clients | Developer exception page enabled in production |
| Inconsistent responses | Local try-catch blocks everywhere |
| Missing logs | Exceptions swallowed silently |
| Incorrect status codes | Every exception returns HTTP 500 |
| Difficult debugging | No correlation IDs in logs |
| Sensitive information exposed | Internal exception messages returned directly |
Most exception handling issues stem from inconsistent implementation rather than framework limitations.
Best Practices
Use centralized exception handling middleware.
Return consistent ProblemDetails responses.
Log every unexpected exception.
Map business exceptions to appropriate HTTP status codes.
Include correlation IDs in logs.
Hide sensitive implementation details from clients.
Monitor exception rates using your observability platform.
Common Anti-Patterns
Avoid these common mistakes:
Wrapping every controller action in try-catch.
Returning stack traces in production.
Swallowing exceptions without logging.
Returning HTTP 200 for failed operations.
Using generic HTTP 500 responses for validation errors.
Exposing database or server details in error messages.
FAQ
Should every controller use try-catch?
No. Most unhandled exceptions should be processed by centralized exception handling middleware. Use try-catch only when you can recover from a specific exception locally.
What is ProblemDetails?
ProblemDetails is a standardized error response format defined by RFC 7807. It helps APIs return consistent and machine-readable error information.
Should exception details be returned to clients?
Only when they are safe and useful. Avoid exposing stack traces, SQL queries, connection strings, or other internal implementation details.
How should exceptions be monitored in production?
Use structured logging together with Application Insights, OpenTelemetry, Seq, Elasticsearch, or another observability platform to monitor exception frequency, trends, and root causes.
Conclusion
Global exception handling is a fundamental part of building reliable ASP.NET Core applications. By centralizing error handling, returning standardized ProblemDetails responses, and logging unexpected failures consistently, you can improve both application security and developer productivity.
Rather than scattering exception handling logic throughout your codebase, implement a single, well-defined exception handling pipeline that produces consistent responses, protects sensitive information, and simplifies production troubleshooting.