Building robust and production-ready ASP.NET Core Web API applications requires a consistent and centralized error handling strategy. In real-world enterprise applications, unhandled exceptions can lead to inconsistent HTTP responses, security vulnerabilities, and poor client experience. Implementing Global Exception Handling in ASP.NET Core ensures structured error responses, proper HTTP status codes, and better observability across distributed systems.
In this article, we will explore how to implement global exception handling in ASP.NET Core Web API using Middleware and the built-in exception handling mechanisms.
Why Global Exception Handling is Important
In ASP.NET Core Web API development, exceptions can occur due to validation failures, database errors, business rule violations, or unexpected runtime failures. Without a centralized exception handling mechanism:
Controllers become cluttered with try-catch blocks.
Error responses become inconsistent.
Sensitive information may leak in production.
Logging and monitoring become fragmented.
Global exception handling solves these problems by intercepting all unhandled exceptions in a single place and returning standardized JSON error responses.
Default Exception Handling in ASP.NET Core
ASP.NET Core provides built-in support through the UseExceptionHandler middleware. In development, the Developer Exception Page displays detailed stack traces. However, in production environments, we should never expose internal exception details.
Basic configuration in Program.cs:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/error");
}
app.MapControllers();
app.Run();
This approach redirects exceptions to a specific endpoint. While useful, it is limited for advanced API scenarios. A custom middleware approach gives full control.
Implementing Custom Global Exception Middleware
Creating a custom middleware is the recommended enterprise approach for global exception handling in ASP.NET Core Web API.
Step 1: Create Exception Middleware
using System.Net;
using System.Text.Json;
public class GlobalExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<GlobalExceptionMiddleware> _logger;
public GlobalExceptionMiddleware(RequestDelegate next, ILogger<GlobalExceptionMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unhandled exception occurred.");
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
var response = new
{
StatusCode = context.Response.StatusCode,
Message = "An unexpected error occurred.",
Detailed = exception.Message
};
var jsonResponse = JsonSerializer.Serialize(response);
return context.Response.WriteAsync(jsonResponse);
}
}
Step 2: Register Middleware in Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
var app = builder.Build();
app.UseMiddleware<GlobalExceptionMiddleware>();
app.MapControllers();
app.Run();
Now, all unhandled exceptions across controllers and services will be captured by this middleware.
Handling Custom Exceptions with Proper HTTP Status Codes
Enterprise Web API applications often require domain-specific exceptions such as NotFoundException, ValidationException, or UnauthorizedAccessException.
Modify the handler to return different status codes:
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
context.Response.ContentType = "application/json";
context.Response.StatusCode = exception switch
{
KeyNotFoundException => StatusCodes.Status404NotFound,
UnauthorizedAccessException => StatusCodes.Status401Unauthorized,
ArgumentException => StatusCodes.Status400BadRequest,
_ => StatusCodes.Status500InternalServerError
};
var response = new
{
StatusCode = context.Response.StatusCode,
Message = exception.Message
};
var jsonResponse = JsonSerializer.Serialize(response);
return context.Response.WriteAsync(jsonResponse);
}
This approach ensures RESTful API best practices by returning meaningful HTTP status codes such as 400 Bad Request, 401 Unauthorized, 404 Not Found, and 500 Internal Server Error.
Using IExceptionHandler (Modern Approach)
ASP.NET Core also supports centralized exception handling using IExceptionHandler for more structured configurations. This allows better separation of concerns and improved testability.
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
builder.Services.AddProblemDetails();
app.UseExceptionHandler();
Custom handler implementation:
using Microsoft.AspNetCore.Diagnostics;
public class GlobalExceptionHandler : IExceptionHandler
{
private readonly ILogger<GlobalExceptionHandler> _logger;
public GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger)
{
_logger = logger;
}
public async ValueTask<bool> TryHandleAsync(
HttpContext httpContext,
Exception exception,
CancellationToken cancellationToken)
{
_logger.LogError(exception, "Unhandled exception");
httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
httpContext.Response.ContentType = "application/json";
var response = new
{
title = "Server Error",
status = httpContext.Response.StatusCode,
detail = exception.Message
};
await httpContext.Response.WriteAsJsonAsync(response, cancellationToken);
return true;
}
}
This method integrates well with ProblemDetails and follows modern ASP.NET Core architecture guidelines.
Best Practices for Global Exception Handling
Never expose stack traces in production environments.
Log exceptions using structured logging (Serilog, NLog, or built-in ILogger).
Return consistent JSON error responses.
Use custom exception types for domain logic.
Integrate monitoring tools for observability.
Testing Global Exception Handling
To test the implementation, create a sample controller endpoint:
[HttpGet("error-test")]
public IActionResult ThrowError()
{
throw new Exception("Test exception from API");
}
Calling this endpoint should return a structured JSON response instead of crashing the application.
Conclusion
Global Exception Handling in ASP.NET Core Web API is essential for building scalable, secure, and maintainable RESTful services. By implementing custom middleware or using the modern IExceptionHandler approach, developers can centralize error handling, enforce consistent HTTP status codes, and improve API reliability. A well-designed exception handling strategy not only enhances client experience but also strengthens logging, monitoring, and overall application architecture in enterprise-grade ASP.NET Core applications.