.NET Core  

Global Exception Handling in ASP.NET Core 8 Using IExceptionHandler

Introduction

Exception handling is an essential part of every ASP.NET Core application. Without proper exception handling, unhandled errors can expose sensitive information, produce inconsistent API responses, and make debugging difficult.

In previous versions of ASP.NET Core, developers commonly used custom middleware or multiple try-catch blocks to handle exceptions. ASP.NET Core 8 introduces a cleaner and more structured approach with the IExceptionHandler interface.

In this article, we'll learn how to implement centralized exception handling using IExceptionHandler and why it's a better approach for modern ASP.NET Core applications.

Why Use Global Exception Handling?

Consider the following controller action:

[HttpGet("{id}")]
public IActionResult GetUser(int id)
{
    try
    {
        var user = _userService.GetUser(id);
        return Ok(user);
    }
    catch (Exception ex)
    {
        return StatusCode(500, ex.Message);
    }
}

While this works, repeating the same try-catch block across multiple controllers results in:

  • Duplicate code

  • Difficult maintenance

  • Inconsistent error responses

  • Poor separation of concerns

A better approach is to handle all unhandled exceptions from a single location.

What is IExceptionHandler?

IExceptionHandler is a built-in interface introduced in ASP.NET Core 8 that provides a centralized mechanism for handling unhandled exceptions.

Instead of catching exceptions inside every controller, ASP.NET Core automatically forwards unhandled exceptions to your custom exception handler.

This keeps controllers focused on business logic while error handling remains centralized.

Step 1: Create a Global Exception Handler

Create a new class implementing IExceptionHandler.

using System.Text.Json;
using Microsoft.AspNetCore.Diagnostics;

public class GlobalExceptionHandler : IExceptionHandler
{
    public async ValueTask<bool> TryHandleAsync(
        HttpContext context,
        Exception exception,
        CancellationToken cancellationToken)
    {
        context.Response.StatusCode = StatusCodes.Status500InternalServerError;
        context.Response.ContentType = "application/json";

        var response = new
        {
            StatusCode = 500,
            Message = "An unexpected error occurred."
        };

        await context.Response.WriteAsync(
            JsonSerializer.Serialize(response),
            cancellationToken);

        return true;
    }
}

Explanation

  • TryHandleAsync() is automatically invoked when an unhandled exception occurs.

  • A JSON response is returned to the client.

  • Returning true indicates that the exception has been handled successfully.

Step 2: Register the Exception Handler

In Program.cs, register the handler.

builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
builder.Services.AddProblemDetails();

Enable the middleware.

app.UseExceptionHandler();

Your global exception handling is now configured.

Step 3: Simplify Your Controllers

Your controller no longer needs repetitive exception handling.

[HttpGet("{id}")]
public IActionResult GetUser(int id)
{
    var user = _userService.GetUser(id);
    return Ok(user);
}

If an exception occurs anywhere inside the action, it is automatically handled by GlobalExceptionHandler.

Returning Different Status Codes

You can return different HTTP status codes depending on the exception type.

context.Response.StatusCode = exception switch
{
    KeyNotFoundException => StatusCodes.Status404NotFound,
    UnauthorizedAccessException => StatusCodes.Status401Unauthorized,
    _ => StatusCodes.Status500InternalServerError
};

This provides more meaningful responses to API consumers.

Production Considerations

Avoid returning raw exception messages in production because they may expose sensitive implementation details.

Instead of returning:

{
    "message": "SQL Server connection failed."
}

Return a generic message:

{
    "message": "An unexpected error occurred."
}

Log the complete exception using a logging framework such as Serilog, NLog, or the built-in ASP.NET Core logging provider.

Best Practices

  • Use a single global exception handler for unhandled exceptions.

  • Return consistent error responses across the application.

  • Map known exceptions to appropriate HTTP status codes.

  • Log exceptions instead of exposing implementation details.

  • Use local try-catch blocks only when you can recover from the exception.

Conclusion

IExceptionHandler provides a modern and maintainable way to handle exceptions in ASP.NET Core 8. By centralizing exception handling, you eliminate repetitive code, improve consistency, and make your application easier to maintain.

For new ASP.NET Core 8 projects, IExceptionHandler should be the preferred approach over scattering try-catch blocks throughout your controllers.

Thank you for reading. I hope this article helps you implement cleaner and more maintainable exception handling in your ASP.NET Core applications.