- Application crashes
- Inconsistent error responses
- Security risks (stack traces exposed)
- Difficult debugging and monitoring
- Why is global exception handling needed
- How to implement a custom global exception middleware
- Best practices used in production systems
Why Do We Need Global Exception Handling?
- Database connection failure
- Null reference access
- Invalid user input
- External API timeout
- Crash the application
- Leak sensitive information
- Return inconsistent responses
- Make debugging extremely difficult
- Catches all unhandled exceptions in one place
- Logs them consistently
- Converts them into a standard HTTP response
- Prevents sensitive details from reaching clients
{ "error": "Object reference not set to an instance of an object" } Worse Case: System.NullReferenceException at OrderService.cs line 42
- Internal code details exposed
- Frontend doesn’t know how to handle different formats
- Logs may be missing or incomplete
{ "statusCode": 400, "message": "Invalid request", "traceId": "abc123" }
- Without global handling → every room handles its own security
- With global handling → one central security desk handles all issues
Interview Answer: Global exception handling is needed to centrally catch all unhandled exceptions, return consistent and secure error responses, improve maintainability, and enable reliable logging without duplicating try–catch blocks across the application.
How To Handle Exceptions in ASP.NET Core?
ASP.NET Core provides multiple ways to handle exceptions, but not all are equal. A senior .NET developer is expected to choose the right approach based on maintainability, security, and scalability.
1. Try-Catch Block in Controller: Handling exceptions inside each controller action using try–catch.
[HttpGet("{id}")] public IActionResult GetProduct(int id) { try { var product = _service.GetProduct(id); return Ok(product); } catch (Exception ex) { return StatusCode(500, ex.Message); } }
- Code duplication: try–catch in every action
- Inconsistent responses: Different error formats
- Security risk: Exception messages exposed
- Poor separation: Business + error handling mixed
- Hard to maintain: Changes needed everywhere
app.UseExceptionHandler(errorApp => { errorApp.Run(async context => { context.Response.StatusCode = 500; await context.Response.WriteAsync("Something went wrong"); }); });
- Limited customization
- No exception type mapping
- No structured response
- Not ideal for APIs
public class NotFoundException : Exception { public NotFoundException(string message) : base(message) { } }
public class ErrorResponse { public int StatusCode { get; set; } public string Message { get; set; } public string TraceId { get; set; } }
public class GlobalExceptionMiddleware { private readonly RequestDelegate _next; private readonly ILogger<GlobalExceptionMiddleware> _logger; public GlobalExceptionMiddleware(RequestDelegate next, ILogger logger) { _next = next; _logger = logger; } public async Task InvokeAsync(HttpContext context) { try { await _next(context); } catch (Exception ex) { _logger.LogError(ex, "Unhandled exception"); int statusCode = ex switch { NotFoundException => 404, _ => 500 }; var response = new ErrorResponse { StatusCode = statusCode, Message = statusCode == 500 ? "Internal error" : ex.Message, TraceId = context.TraceIdentifier }; context.Response.StatusCode = statusCode; context.Response.ContentType = "application/json"; await context.Response.WriteAsJsonAsync(response); } } }
app.UseMiddleware<GlobalExceptionMiddleware>();
public class GlobalExceptionHandler : IExceptionHandler { public async ValueTask<bool> TryHandleAsync( HttpContext context, Exception exception, CancellationToken cancellationToken) { context.Response.StatusCode = exception switch { NotFoundException => 404, _ => 500 }; await context.Response.WriteAsJsonAsync(new { message = "Error occurred", traceId = context.TraceIdentifier }, cancellationToken); return true; } }
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
app.UseExceptionHandler();
- Clean & structured
- Official Microsoft approach
- Easy integration with ProblemDetails
- Less boilerplate
Interview Answer: For production-grade ASP.NET Core APIs, I prefer custom global exception middleware because it gives full control over exception mapping, logging, and response structure. For newer .NET versions, IExceptionHandler is also a clean and modern alternative.




