In Java 1.4, java.lang.Throwable finally allowed for nested Throwables. This may not seem like a big breakthrough, but in a world of n-tier application, low level exceptions from driver objects are not appropriate to return to end users. Also, loosing the root cause introduced its own set of problems.
In a Java 1.3 world, we resorted to:
public class AbstractRecursiveException extends java.lang.Exception implements java.io.Serializable
/** * Constructs an instance of <code>AbstractRecursiveException</code> with the specified detail message. * @param _cause the extception which caused this exception. * @param _cause the extception which caused this exception. **/ public AbstractRecursiveException(String _msg, Throwable _cause);
/** * @return the parent cause of this Exception */ public Throwable getCause();
Now with Java 1.7 almost upon us, I would like to ask for some more improvements to this core framework for those of us who live in an n-tier web based world:
- error code support
- user error status
- remediation messaging
Error Code Support
What is the difference between:
- User Cannot Login due to authentication service being unavailable
- User Cannot Login due to database being down
- User Cannot Login due to incorrect password
In addition to placing a message on an Exception, I want:
In complex systems built on top of many services, you can see the same end exception for many different reasons. Not all of the time do you want to expose all the various details to the end user, but you still want front end systems to be able to react or at least track differently.
For one of my applications, we started to monitor the login failures and found it imperative to be able to differentiate when one of our authentication providers was the cause, our own systems, or when the user simply entered the wrong credentials.
Because the backend issues were usually transient, we simply told the user to try again in all cases, but by watching the error codes we could see what was the cause quickly.
I use my Exceptions to drive user messaging. Nothing is more annoying than having a mapper class to translate various Exceptions to the right message for the end user. Now do it for different languages and locals.
By using a codes, we could keep simple resource and String files. Each time we added another language, we could ship the error resource file to the translation house and have all of our error messaging ready to go. No code no fuss.
If you are going to use error codes, steal ideas from other systems. Just like HTTP, we reserved ranges for our error codes.
# 2000-3000: User Data Validation Errors # 2000-2100: basic user data validation error 2000: Generic User Data Validation Error 2001: Incorrect Data Type 2002: Data out of range 2003: Invalid numeric 2004: String contains illegal characters 2005: Invalid email address format 2006: Invalid phone number format #2101: User Account Data Errors 2100: User name is too long 2101: User name is too short...
By using ranges, it was easier to lump related errors together and to deal with them as groups. For example, all User Data Validation errors can be feed back to the user for correction.
Finally, with ranges, we had an easier time writing and maintaining code that dealt with Exceptions.
For example, instead of creating a myriad of different, very specific Exception classes, we created a single set of UserDataValidationException. This generic Exception was use by many of the front end components that validated user data before passing it on.
If it was thrown, front end branch logic could key from the code or the range to route the presentation layer to the correct code.
Like many web apps, we have a standard set if widgets to display error messages to end users. We use resourve bundles and the error code to give the best message to the user.
Better yet, we embed the error code in the URL. With our existing web analytic tools I can tell you how many errors we served, which ones we served, and how they are trending. There is little that lets your see into the quality of your user experience more than real time views of your error stream.
Once you know where your application is failing, or your users keep getting confused; you can take control of your application and making it better.
User Error Status
Many times the errors are the fault of some failure in the system. Other times it is simple user error. When running a web system, tracking the number of errors you are serving is a key metric.
But when you come in one day and see a 234% spike in error messages served by the system, you want to know if someone is running a script that is trying to fake CAPTCHA or if something server side failed. You don’t want to have to guess in front of your CEO, “Maybe a bunch of users fat fingered their passwords this morning?”
When this first came up, we dug through all of our error code and divided them up into probably caused by us and probably caused by them categories.
We finally added:
As your can guess, the sources were:
- our systems
- our users
- external systems 1 though N
With this added to our tracking we quickly tracked down places were users kept running into user errors. This told us that the part of the application didn’t make sense and needed to be re factored or made more user friendly.
Or we found that certain third-party systems were not as reliable as their SLA indicated and we needed to spend more time on monitoring or finding replacements.
Or we found bugs that users were not reporting to customer support, but were costing us users.
But most importantly, it stopped the finger pointing between our vendors, QA, Developers, and Customer Support.
In the web world, you run into a lot of “Exceptions” that are fixable by the end user:
- validate your email first
- this file doesn’t have permissions to be shared
- your privacy settings don’t allow this
- update your credit card on file
- please accept our new terms of service
These errors take users out of the flow.
It is a hassle for Front End developers to keep track of all of these exceptions and to write all the user friendly ways to address each one.
Fortunately in the web world, a generic fix is suitable 90% of the time. We updated our error widget to look for a remedy path. If available, instead of just telling the user what went wrong, we told him how to fix it:
- Dear user X didn’t work
- You need to do Y before you try again
- Click on Z URL and we will pop up a dialog for you to do Y
So by adding:
Our exceptions could power error messaging that took us from user caused errors to user work flows. It closed the circuit between problem and solution and dramatically reduced the email load for our customer support queue.