How to write an error message a user can act on.

•

•

Tell Them What to Do, Not What Broke

I once spent three hours on a Tuesday morning debugging a failed payment integration, only to realize the system was throwing a generic “Internal Server Error” because a single field was formatted incorrectly. It wasn’t a complex architectural failure or a race condition; it was just a bad message. Most of the industry seems to think that solving this requires a massive UX overhaul or a sophisticated AI-driven diagnostic engine, but that’s nonsense. In reality, learning how to write an error message a user can act on is usually just about stopping the habit of hiding behind vague, defensive technical jargon that tells the user nothing while making our lives harder.

I’m not here to sell you on some high-level design philosophy or a new framework for error handling. I want to talk about the practical, slightly painful lessons I’ve learned from being the person on call when things go sideways. I’ll show you how to move past the “something went wrong” era and start providing actual context and direction. We’ll look at the trade-offs between security and clarity, and how to build messages that actually help people fix their own mistakes instead of opening a support ticket they don’t need to file.

Table of Contents

The Cost of Vague Microcopy for Error States

The Cost of Vague Microcopy for Error States.

I once spent an entire weekend debugging a silent failure in a payment gateway integration, only to realize the frontend was swallowing a specific 402 error and replacing it with a generic “Something went wrong” toast. That’s the hidden tax of bad microcopy for error states. When we provide vague feedback, we aren’t just failing at UX; we are actively increasing the cognitive load on both the user and our own support team. Instead of a quick fix, the user is left staring at a screen, wondering if it’s their credit card, our API, or just a momentary glitch in the matrix.

This lack of clarity creates a feedback loop of friction. If a user hits a wall and doesn’t know why, they don’t just try again—they get frustrated, they lose trust, and eventually, they abandon the flow entirely. From an engineering perspective, poor user interface error handling also means more “ghost” tickets in our backlog. We end up chasing down logs to figure out what a user experienced, when a simple, descriptive message could have solved the problem in seconds. Reducing user frustration with error messages isn’t just about being polite; it’s about building a system that is actually maintainable and predictable when things inevitably go sideways.

How Poor User Interface Error Handling Causes Chaos

How Poor User Interface Error Handling Causes Chaos

I remember a deployment at the logistics startup where a simple validation error turned into a support ticket nightmare. We had a field that wouldn’t accept a specific character format, but the UI just threw a generic “Invalid Input” flag. Because the user interface error handling was so shallow, users didn’t know if they had typed a special character, exceeded a length limit, or if the system was just being temperamental. They ended up refreshing the page, losing their entire form entry, and eventually just giving up on the checkout process entirely.

When you fail at improving error message clarity, you aren’t just dealing with a minor UX hiccup; you are creating a feedback loop of chaos. In my experience, poor error handling leads to “rage clicking,” where users hammer a button repeatedly because they don’t realize the system is actually waiting for a specific correction. This doesn’t just frustrate the human on the other side of the screen—it sends a massive, unnecessary spike of redundant requests to our backend. It’s a classic case of bad microcopy causing actual infrastructure strain.

My rules of thumb for not making people hate your software

  • Stop using “System Error” or “Something went wrong.” These are useless placeholders that tell the user nothing about whether they should wait five minutes or call their IT department. If you can’t fix it on the backend, tell them what they can actually do, like “Check your internet connection” or “Verify your credit card number.”
  • Avoid the “Security through Obscurity” trap in error messages. I’ve seen engineers block login errors with “Invalid credentials” to prevent user enumeration, which is fine, but if a user is genuinely locked out, they need to know if it’s a password issue or an account suspension. Find the balance between being secure and being helpful.
  • Never point to a stack trace in the UI. I know it feels like “complete information,” but showing a user a raw Java exception is just noise. It’s confusing for them and provides zero actionable value. If you need that data for debugging, log it to your observability tool and give the user a unique correlation ID instead.
  • Match the tone to the severity. If a user’s file failed to upload because it’s too large, a polite “File exceeds 10MB limit” is fine. If the entire database is down and they’ve lost an hour of work, a cheery “Oopsie!” is going to make them want to throw their laptop. Keep it matter-of-fact.
  • Always provide the “Next Step.” An error message without a way out is just a dead end. If the payment failed, provide a button to “Try another card.” If the search returned nothing, suggest “Broaden your filters.” Don’t just tell them they’re stuck; show them the exit.

Lessons from the wreckage

Stop treating error messages as a way to dump a stack trace; if a user sees a UUID or a “NullPointerException,” you haven’t solved the problem, you’ve just exported the technical debt to the customer.

Every error state needs a clear “next step”—either a way to fix it (like checking a credit card number) or a way to bypass it—otherwise, you’re just leaving them stuck in a dead end.

Design for the person who is having a bad day; a vague error message is a friction point that turns a minor hiccup into a support ticket, and support tickets are just expensive ways to fix bad microcopy.

The Post-Mortem on Error Messages

The Post-Mortem on Error Messages guide.

At the end of the day, writing better error messages isn’t about finding the perfect, most polite phrasing or following some arbitrary UX design trend. It’s about reducing the cognitive load on the person on the other side of the screen when things inevitably go wrong. We’ve seen how vague microcopy creates a vacuum of information that users fill with frustration, and how poor UI handling turns a minor hiccup into a systemic meltdown. If you can provide a clear reason for the failure and, more importantly, a specific path toward a resolution, you’ve done half the work of a good engineer. It’s the difference between telling someone “the bridge is out” and telling them “the bridge is out, so take the detour via Route 9 instead.”

I’ve spent enough time staring at logs in the middle of the night to know that most “unforeseen” disasters are just the result of small, preventable communication gaps. When we treat error messages as an afterthought—something to be tacked on during the final sprint—we are essentially building technical debt into our user experience. My advice is to treat your error states with the same rigor you apply to your core business logic. If you write your error messages as if you are leaving a note for your future self to fix a production incident, you’ll find that your users—and your on-call rotation—will thank you for it.

Frequently Asked Questions

How do we balance giving enough technical detail for a developer to debug without scaring the actual end-user?

The trick is decoupling the “what” from the “why.” For the user, keep it human: “We couldn’t save your changes because of a connection hiccup.” For us, that’s useless.

At what point does a "helpful" error message become too long and just clutter the UI?

It’s a balance of signal versus noise. If the message takes more than two seconds to read, you’ve probably written a paragraph where you needed a sentence. I usually follow a simple rule: give them the “what” and the “how” in one breath. If the fix requires a complex sequence of steps, don’t cram them into a tiny tooltip. Put the brief instruction in the UI and link to a dedicated troubleshooting page.

If we're dealing with a cascading failure in a distributed system, how do we decide which specific error to bubble up to the user?

When a distributed system starts collapsing, the instinct is to pass the specific exception up the chain. Don’t. If your payment service is failing because the auth service is timing out, telling the user “Auth service timeout” is useless noise.

How much of the error-handling logic should live in the frontend versus the backend to ensure the message is actually accurate?

I’ve seen too many “Internal Server Error” messages that are actually just a frontend guessing what went wrong. The source of truth has to be the backend; it knows if the database is down or if a specific validation failed. The frontend should act as a translator, not a judge. It receives a machine-readable code from the API and maps it to a human-friendly instruction. If the frontend tries to guess the logic, you’ll eventually end up lying to your users.

Tomasz Wierzbicki

About Tomasz Wierzbicki

I am not interested in arguing about frameworks. I am interested in why the thing broke at two in the morning and what would have prevented it. Most of what I know came from being wrong in production and writing it down afterwards.