Show users a generic error and keep the details on the server. A stack trace, SQL error or file path in a response tells an attacker how your app is built. Handle errors in one global place, log the full exception privately, and use status codes that match who caused the problem.
What users should see
The OWASP Error Handling Cheat Sheet says to return a generic message that reveals no implementation details. Its example is a short JSON body saying an error occurred and to retry. Optionally use the RFC 9457 problem details format (the cheat sheet notes it replaces RFC 7807) so clients can parse errors consistently.
A good user-facing error has three parts: a stable code, a plain sentence, and a reference ID the user can quote to support. The reference ID links the user's report to your private log entry without revealing anything.
What you should log
Log the full exception on the server for investigation, and never return those details to the user. The cheat sheet also suggests monitoring 5xx errors, since they can show the application failing on certain inputs. That makes an error spike a useful early signal, sometimes of someone probing your inputs.
For what to put in logs and what to keep out, see what to log in a web app.
Status codes
Use 5xx only for requests the application cannot fulfill. Use 4xx when the client caused the problem, such as unauthorized access or an oversized body. Misuse hurts in both directions: reporting your own bug as a 400 hides it from monitoring, and reporting a normal refusal as a 500 creates noise.
Use one global handler
Define a single global error handler in the application's runtime configuration so unexpected errors always get a generic response. The cheat sheet gives examples for several stacks:
- Java: an error page mapped to
java.lang.Exceptioninweb.xml. - Spring: a
@RestControllerAdviceclass with an@ExceptionHandlerforException. - ASP.NET Core:
UseExceptionHandlerwith a dedicated error controller. - ASP.NET Web API: custom
IExceptionLoggerandIExceptionHandlerimplementations.
Developer exception pages are fine in development but should not be enabled in production.
The checklist
- Trigger an error on purpose in production-like settings: a malformed request, a missing record, a forced exception in staging. Does the response show anything internal?
- Search responses for file paths, class names, SQL fragments and library versions.
- Confirm debug mode is off in production configuration. Check it after each deploy, because defaults often come back with new config.
- Make sure 404 and 403 responses do not tell an outsider more than needed, for example whether an account exists.
- Check that the global handler also covers background jobs and API routes, not just pages.
- Verify that the reference ID in the response matches a log entry.
- Alert on a rise in 5xx.
Common mistakes
- Different error text for "user not found" and "wrong password", which lets an attacker list valid accounts.
- Returning the raw exception message "to help the front end".
- A catch block that swallows an error and returns partial data.
- Verbose errors from a third-party library that bypass your handler.
The cheat sheet recommends covering all possible failure modes, so test the unhappy paths as seriously as the happy ones.
Quick check
Is it fine to show detailed errors to admins?
Avoid it in production. Admins can read the private log using a reference ID, which keeps details off the wire.
Should a 500 ever include a message?
A generic one, plus a reference ID. Never the exception text.
Sources
Last reviewed: 19 October 2026.