Global Exception Handling
Question Variations
- "Why is it important to have a global exception handling strategy instead of individual try-catch blocks?"
- "How do you prevent sensitive system information from being leaked in error messages?"
- "Explain how you would implement a global exception handler in your preferred web framework."
- "What should be included in a standard error response returned by an API?"
Why This Is Asked
Production-grade applications must handle errors gracefully without leaking sensitive information. This question tests your ability to centralize error logic instead of littering your code with try-catch blocks.
Key Concepts
- Centralization: A single location to catch and log all unhandled exceptions.
- User Experience: Returning a consistent error response format to the client.
- Security: Preventing raw stack traces from reaching the end-user.
- Implementation: Usually done via Middleware (ASP.NET Core), Exception Filters, or ControllerAdvice (Spring).
Question Variations
- “Why is it important to have a global exception handling strategy instead of individual try-catch blocks?”
- “How do you prevent sensitive system information from being leaked in error messages?”
- “Explain how you would implement a global exception handler in your preferred web framework.”
- “What should be included in a standard error response returned by an API?”
Answers by Technology
+ Add VariantExpected Answer (.NET 10 / C# 14)
In modern ASP.NET Core, global exception handling is implemented using the IExceptionHandler interface (introduced in .NET 8) or custom middleware.
Real-time Example: When any service throws an UnauthorizedException, the global handler catches it, logs the event, and returns a 401 Unauthorized JSON response with a friendly message.
Why It Matters
It prevents the application from crashing and ensures the client always receives a structured response (like ProblemDetails) instead of an ugly HTML error page or a raw stack trace, which is a security risk.
Code Example
public class GlobalExceptionHandler : IExceptionHandler
{
public async ValueTask<bool> TryHandleAsync(
HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
{
var response = new { Message = "An unexpected error occurred." };
httpContext.Response.StatusCode = 500;
await httpContext.Response.WriteAsJsonAsync(response, cancellationToken);
return true; // Exception handled
}
}
// Registration in Program.cs
// builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
// app.UseExceptionHandler(_ => { });
Common Mistakes
- Leaking sensitive info: Returning
exception.Messageorexception.StackTracein production. - Not logging: Catching the exception but failing to log it for developers to see.
Follow-up Questions
- What is
ProblemDetails? (Answer: A standardized JSON format for returning machine-readable error details in HTTP responses). - Middleware vs Exception Filters? (Answer: Middleware handles everything in the pipe; Filters only handle exceptions within the MVC/Controller context).
Expected Answer (Java 26 / Spring Boot)
In Spring Boot, global exception handling is primarily done using the @ControllerAdvice and @ExceptionHandler annotations.
Real-time Example: Creating a RestResponseEntityExceptionHandler that catches EntityNotFoundException and returns a 404 Not Found with a custom error body.
Why It Matters
Centralized error handling ensures that no matter where an error occurs in your application, the client always gets a consistent, secure, and clean response. It avoids the need for repetitive try-catch blocks in every controller method.
Code Example
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = { IllegalArgumentException.class })
protected ResponseEntity<Object> handleConflict(RuntimeException ex, WebRequest request) {
String bodyOfResponse = "Invalid arguments provided.";
return new ResponseEntity<>(bodyOfResponse, HttpStatus.BAD_REQUEST);
}
}
Common Mistakes
- Catching
Exception.class: Catching the root Exception class can hide bugs and prevent specific handlers from running. Always try to catch specific exceptions. - Security Leaks: Returning the internal error message or stack trace to the user, which might reveal database structure or library versions.
Follow-up Questions
ErrorControllervs@ControllerAdvice? (Answer:ErrorControllerhandles errors outside of the Spring MVC context, like 404s for non-existent routes).- What is
ProblemDetail? (Answer: Introduced in Spring Boot 3 / RFC 7807, it’s the standardized way to return API error details).
Expected Answer (PHP 8.5 / Laravel)
In Laravel, all exceptions are handled by the App\Exceptions\Handler class (or in newer versions, via bootstrap/app.php configurations).
Real-time Example: Catching a ModelNotFoundException globally and returning a custom 404 JSON response for API requests, instead of the default HTML error page.
Why It Matters
Global handling prevents your app from leaking technical details (like database queries or file paths) when an error occurs. It also allows you to integrate with third-party monitoring tools like Sentry or Bugsnag in one place.
Code Example
// In bootstrap/app.php (Laravel 11+)
->withExceptions(function (Exceptions $exceptions) {
$exceptions->render(function (NotFoundHttpException $e, Request $request) {
if ($request->is('api/*')) {
return response()->json([
'message' => 'Record not found.'
], 404);
}
});
})
Common Mistakes
- Displaying errors in production: Having
APP_DEBUG=truein production is a critical security vulnerability. - Not logging: Using a global handler to “silence” errors without logging them for developers to fix.
Follow-up Questions
- What is the ‘report’ method? (Answer: The method used to log exceptions or send them to external services like Sentry).
- What is ‘render’? (Answer: The method responsible for converting an exception into an HTTP response).
Expected Answer (Express 5.1.0 / Node.js 18+)
Express centralizes request errors with error-handling middleware, declared after routes and normal middleware with the signature (err, req, res, next). Route code should throw or reject for exceptional failures; Express 5 forwards rejections from returned promises to the error chain. Callback-style asynchronous APIs remain different: pass their error to next(err) because a later callback is outside the route handler’s synchronous execution.
The global handler should classify known operational errors into safe status codes and a consistent response body, log the original error with a request ID, and return a generic 500 response for unexpected failures. Do not expose stacks, SQL messages, tokens, or internal hostnames. If res.headersSent is true, delegate with next(err) so Express’s default handler can close or complete the already-started response correctly.
Why It Matters
A single response policy prevents information leaks and gives clients predictable failures while preserving the diagnostic detail operators need.
Code Example
import express, { NextFunction, Request, Response } from 'express';
const app = express();
app.get('/users/:id', async (_req: Request, _res: Response) => { throw new Error('database unavailable'); });
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
console.error({ err, path: req.path });
if (res.headersSent) return next(err);
return res.status(500).json({ error: 'internal server error' });
});
app.listen(3000);
Common Mistakes
- Registering the error handler before routes: Errors from later middleware will not reach it as intended.
- Returning
err.messagefor unknown errors: Internal implementation details can be exposed to clients.
Follow-up Questions
- Why must an Express error handler take four arguments? (Answer: Express uses that signature to classify it as error middleware.)
- What changes for a streaming response? (Answer: If headers were sent, delegate to the default handler rather than writing a second response.)
Expected Answer (Koa 3.2.1 / Node.js 18+)
Koa centralizes request errors in outer middleware: try { await next(); } catch (error) { ... }. It must be registered first so it wraps routes, routers, and later middleware. Map known safe failures to a stable response, log the actual exception and request context, and return a generic 500 for unexpected errors. Use ctx.throw() for expected HTTP failures rather than manually creating inconsistent response bodies.
Why It Matters
Centralization stops sensitive errors escaping and gives every API endpoint the same failure contract.
Code Example
import Koa, { Context, Next } from 'koa';
const app = new Koa();
app.use(async (ctx: Context, next: Next) => {
try { await next(); }
catch (error) { ctx.status = (error as { status?: number }).status ?? 500; ctx.body = { error: 'request failed' }; }
});
app.use((ctx: Context) => { ctx.throw(403, 'forbidden'); });
app.listen(3000);
Common Mistakes
- Adding the error boundary last: It does not wrap preceding failures.
- Sending exception details to clients: Stack traces and service details can leak.
Follow-up Questions
- What does
ctx.throw(400)do? (Answer: Throws an HTTP-aware error that outer middleware can map.) - What is Koa’s error event for? (Answer: App-level logging and observability.)