Isaac.

Implement Global Exception Handling

A practical guide with examples for ASP.NET, Spring Boot, Express, Next.js, Flask, and Laravel.

Why global exception handling matters

Centralizing how your application deals with unexpected errors provides consistency, improves security (by avoiding leaking stack traces), and simplifies monitoring and support. Instead of sprinkling try/catch everywhere, a single place can translate exceptions into user-friendly responses and log diagnostics for developers.

Common patterns

  • Middleware / filter / interceptor: Insert into request pipeline to catch exceptions for all routes.
  • Controller advice / global handler: Framework-specific construct that maps exceptions to responses.
  • Wrapper helpers: Higher-order functions that wrap handlers to catch errors (common in serverless/Next.js).
  • Typed errors: Throw domain-specific exceptions that the global handler maps to proper HTTP status codes.

Framework examples

ASP.NET Core

Use a middleware to catch all exceptions and return a JSON response for APIs. Place it early in the pipeline so all downstream errors are caught.

// ASP.NET Core - Global exception handling using middleware
// File: Middlewares/ErrorHandlingMiddleware.cs
using System.Net;
using System.Text.Json;

public class ErrorHandlingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<ErrorHandlingMiddleware> _logger;

    public ErrorHandlingMiddleware(RequestDelegate next, ILogger<ErrorHandlingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Unhandled exception");
            context.Response.ContentType = "application/json";
            context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;

            var result = JsonSerializer.Serialize(new { message = "An unexpected error occurred." });
            await context.Response.WriteAsync(result);
        }
    }
}

// In Program.cs / Startup.cs register the middleware with app.UseMiddleware<ErrorHandlingMiddleware>();

Explanation: This middleware wraps the request pipeline and logs the exception. It sets a 500 status and returns a minimal JSON message to avoid exposing internals. Register with app.UseMiddleware<ErrorHandlingMiddleware>();.

Spring Boot

Spring's @ControllerAdvice lets you map exceptions across controllers to HTTP responses.

// Spring Boot - Global exception handling using @ControllerAdvice
// File: advice/GlobalExceptionHandler.java
package com.example.advice;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public ResponseEntity<Object> handleAll(Exception ex) {
        // Log the exception (omitted)
        var body = java.util.Map.of("message", "An unexpected error occurred.");
        return new ResponseEntity<>(body, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

Explanation: @ControllerAdvice is scanned globally. The handler returns a JSON body and status 500. You can add handlers for specific exception types (e.g. EntityNotFoundException) to return 404.

Express (Node.js)

Express recognizes error-handling middleware by the four parameters signature (err, req, res, next).

// Express - Global error handler
// File: app.js
const express = require('express');
const app = express();

app.get('/', (req, res) => {
  throw new Error('Boom');
});

// Error handling middleware must have 4 args: (err, req, res, next)
app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).json({ message: 'An unexpected error occurred.' });
});

module.exports = app;

Explanation: Place this middleware after your routes. It logs the error and returns a standard JSON response. For production, avoid printing entire stack traces to the client.

Next.js (API routes)

Next.js API routes are just functions — wrap them with a higher-order function to catch errors consistently.

// Next.js API Route - Centralized error helper
// File: pages/api/_error.ts
import type { NextApiRequest, NextApiResponse } from 'next'

export function withErrorHandler(handler: (req: NextApiRequest, res: NextApiResponse) => Promise<void>) {
  return async (req: NextApiRequest, res: NextApiResponse) => {
    try {
      await handler(req, res)
    } catch (err) {
      console.error(err)
      res.status(500).json({ message: 'An unexpected error occurred.' })
    }
  }
}

// Usage in pages/api/example.ts
// export default withErrorHandler(async (req, res) => { throw new Error('fail') })

Explanation: The withErrorHandler wrapper calls a handler and catches exceptions, returning 500. Reuse this wrapper across API routes to keep behaviour consistent.

Flask (Python)

Flask's @app.errorhandler registers a function to handle exceptions and return a response.

# Flask - Global error handler
# File: app.py
from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/')
def index():
    raise RuntimeError('boom')

@app.errorhandler(Exception)
def handle_exception(e):
    # Log the exception here
    return jsonify({'message': 'An unexpected error occurred.'}), 500

if __name__ == '__main__':
    app.run()

Explanation: The error handler receives the exception and returns a JSON response with a 500 status. For APIs, check the request type and return JSON only for API calls.

Laravel (PHP)

Laravel centralizes exceptions in app/Exceptions/Handler.php. Override render to return JSON for API requests.

// Laravel - Handler in app/Exceptions/Handler.php
<?php

namespace AppExceptions;

use Throwable;
use IlluminateFoundationExceptionsHandler as ExceptionHandler;
use IlluminateHttpJsonResponse;

class Handler extends ExceptionHandler
{
    public function render($request, Throwable $e)
    {
        // For API requests return JSON
        if ($request->expectsJson()) {
            return response()->json([
                'message' => 'An unexpected error occurred.'
            ], 500);
        }

        return parent::render($request, $e);
    }
}

Explanation: By detecting $request->expectsJson(), the handler returns a consistent JSON error for API consumers while letting web requests fall back to default HTML error pages.

Best practices

  • Don't leak internals: Return generic messages to clients; store details in logs.
  • Use structured logging: Include correlation IDs and request context so errors are traceable across services.
  • Map domain errors to status codes: Use custom exceptions for validation (400), not found (404), unauthorized (401/403), etc.
  • Return consistent error shape: Example: {"error":"code","message":"...","details":{ }}
  • Monitor & alert: Send critical errors to an error-tracking system (Sentry, Datadog) and create alerts for spikes.

Conclusion

Global exception handling simplifies application maintenance, hardens security, and improves developer productivity. Pick the pattern that fits your framework — middleware, controller advice, or higher-order wrappers — and apply the best practices above to make error handling robust and consistent.