Exception handling is one of the most important aspects of building stable, secure, and production-ready applications. Every application eventually encounters errors: invalid input, network failures, database connection issues, unhandled null values, or unauthorized access. The challenge is not avoiding these errors but handling them gracefully and consistently.
ASP.NET Core provides a rich and powerful exception-handling pipeline. This guide explains exception handling from the basics to advanced techniques, including real-world examples, global middleware, logging, structured API responses, custom exceptions, and best practices.
This article covers the following:
What exceptions are
Why exception handling matters
Types of exceptions
Try–catch–finally
ASP.NET Core built-in error handling
Global exception handling middleware
Exception filters
Logging exceptions
Custom business exceptions
Validation error handling
Returning consistent API error responses
Production best practices
1. What is an Exception?
An exception is an unexpected event that occurs during program execution and disrupts the normal flow of the application.
Common examples include:
Trying to divide by zero
Attempting to read null values
Accessing invalid array indexes
Database connection failures
Invalid type conversions
Exceptions help developers identify problems, but unhandled exceptions cause application crashes. This is why exception handling is essential.
2. Why Exception Handling Is Important
Proper exception handling provides several benefits:
Prevents application crashes
Displays user-friendly messages
Prevents sensitive information from leaking
Produces reliable API responses
Helps developers diagnose issues through logs
Improves maintainability and robustness
Without exception handling, end users may see application crashes or confusing error pages. With proper handling, errors become predictable, controlled, and secure.
3. Types of Exceptions in .NET
System Exceptions
These are built-in .NET exception types, such as:
Application Exceptions
Custom exceptions created by developers to handle logical or business-related issues:
public class InvalidOrderException : Exception
{
public InvalidOrderException(string message) : base(message) { }
}
Business Rule Exceptions
These represent domain-level issues, such as:
Insufficient balance
Invalid order ID
Inactive account
Unauthorized operation
4. Try–Catch–Finally - Basic Exception Handling
The simplest form of exception handling uses try, catch, and finally blocks.
try
{
int x = 10;
int y = 0;
int result = x / y;
}
catch (DivideByZeroException ex)
{
Console.WriteLine("Cannot divide by zero.");
}
catch (Exception ex)
{
Console.WriteLine("An unexpected error occurred.");
}
finally
{
Console.WriteLine("This block always executes.");
}
The finally block is optional and is typically used to release resources like database connections or file handles.
5. Exception Handling in ASP.NET Core
ASP.NET Core offers several layers of exception handling:
Developer exception page
Production exception handler using UseExceptionHandler
Custom global exception handling middleware
Exception filters
Validation responses
Structured error responses (Problem Details)
Logging via ILogger or external log providers
Each technique has a specific use case depending on whether you are in development or production.
6. Developer Exception Page - Development Environment
This provides detailed error information, including:
Enable it only in development:
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
Never enable this in production because it exposes sensitive details.
7. Built-in Production Exception Handler - UseExceptionHandler
This middleware catches unhandled exceptions globally.
Step 1: Enable the handler in Program.cs
app.UseExceptionHandler("/error");
Step 2: Create the error endpoint
app.Map("/error", (HttpContext context) =>
{
return Results.Problem("An unexpected error occurred.");
});
This handles all unhandled exceptions without exposing internal details.
8. Global Exception Handling Middleware - Preferred Method
Creating a custom middleware provides full control and centralizes all exception handling logic.
ExceptionMiddleware.cs
using System.Net;
using System.Text.Json;
public class ExceptionMiddleware : IMiddleware
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
try
{
await next(context);
}
catch (Exception ex)
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
context.Response.ContentType = "application/json";
var errorResponse = new
{
StatusCode = context.Response.StatusCode,
Message = "An unexpected error occurred.",
Detail = ex.Message
};
var json = JsonSerializer.Serialize(errorResponse);
await context.Response.WriteAsync(json);
}
}
}
Register in the Program.cs
builder.Services.AddTransient<ExceptionMiddleware>();
app.UseMiddleware<ExceptionMiddleware>();
This approach is suitable for production APIs.
9. Exception Filters - MVC Specific
Exception filters allow centralized handling for controller actions.
CustomExceptionFilter.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
public class CustomExceptionFilter : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
context.Result = new JsonResult(new
{
Message = "An error occurred.",
Detail = context.Exception.Message
})
{ StatusCode = 500 };
}
}
Register in the Program.cs
builder.Services.AddControllers(options =>
{
options.Filters.Add<CustomExceptionFilter>();
});
Filters are useful when you want exception handling only for controllers, not for middleware-level requests.
10. Validation Exception Handling
ASP.NET Core automatically validates DTOs using data annotations.
Example DTO:
public class RegisterDto
{
[Required]
public string Email { get; set; }
[MinLength(6)]
public string Password { get; set; }
}
When validation fails, ASP.NET Core returns a structured 400 response:
{
"errors": {
"Password": ["The field Password must be a string with a minimum length of 6."]
}
}
No additional exception handling is required for validation errors.
11. Structured Error Responses Using ProblemDetails
ASP.NET Core supports the Problem Details format (RFC 7807).
Example:
return Problem(
title: "Internal Server Error",
detail: "Database connection failed",
statusCode: 500
);
This produces a structured response:
{
"title": "Internal Server Error",
"status": 500,
"detail": "Database connection failed"
}
Consistent error responses improve debugging and client-side error handling.
12. Logging Exceptions Using ILogger
Logging is critical for diagnosing issues and monitoring applications.
try
{
int.Parse("abc");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error while parsing number");
}
Logging providers supported in ASP.NET Core:
Console logging
Debug logging
Serilog
NLog
Seq
Application Insights
Logs should be centralized in production.
13. Custom Business Exceptions
Applications often need domain-level error handling.
public class InsufficientFundsException : Exception
{
public InsufficientFundsException(string message) : base(message) { }
}
Usage:
if (balance < amount)
throw new InsufficientFundsException("Insufficient funds to complete transaction.");
Catch these globally and return user-friendly messages.
14. Real-World Example: Handling Exceptions in a Controller
[HttpGet("{id}")]
public async Task<IActionResult> GetOrder(int id)
{
var order = await _context.Orders.FindAsync(id);
if (order == null)
throw new KeyNotFoundException("Order not found");
return Ok(order);
}
Global middleware converts this into a structured error response without exposing sensitive details.
15. Best Practices for Exception Handling in Production
Never expose internal exception messages to users
Always log exceptions with proper context
Use global exception handling middleware
Standardize error responses with ProblemDetails
Use try-catch only where required
Validate user input before processing
Create custom exceptions for business logic
Return proper HTTP status codes
Separate domain, application, and infrastructure exceptions
Proper exception handling improves code quality, application reliability, and security.
Thank you for reading this complete guide on Exception Handling in ASP.NET Core. Exception handling is a critical skill for building stable, secure, and maintainable applications. By implementing global handlers, logging, structured responses, and custom exception logic, you ensure your APIs remain predictable and professional under all circumstances.