Updated: July 2021
Proper exception handling can save you days in troubleshooting. Unexpected production issues can ruin anyone’s dinner and weekend plans, at any time. Furthermore, your reputation is on the line if you can’t resolve them quickly. A clear policy on exception management will save you diagnosis, reproduction, and correction time. What’s most important, it will give you peace of mind (and some hours back!).
Here are 6 tips on how you too can improve your exception handling.
1. Use a single, system-wide exception class
Don’t use separate classes for each exception type, instead, just create just one. On top of that, make it extend RuntimeException. This streamlines your class count and removes the need to declare exceptions that are not going to be handled anyways.
Now, you may be thinking: How will I tell exceptions apart if they’re all the same type? And how will I track type-specific properties? We cover that in this post.
2. Use enums for error codes
Most developers are trained to put the cause of an exception into its message. This may be acceptable when reviewing log files, but it does have some disadvantages:
- Messages can’t be translated (unless you’re Google).
- Messages can’t be easily mapped to user-friendly text.
- Messages can’t be inspected programmatically.
Putting info in the message also leaves the wording up to each developer, which can lead to different phrases for the same failure.
Putting information in the message leaves the wording of the error up to the developer. This can lead to inconsistency in the use of terms across a team, and you may end up with many different wordings and phrases for the same error, which leads to issues down the line.
To avoid this, simply use enums to specify the exception type. Create one enum for each error category – payments, authentication, etcetera – and make the enums implement an ErrorCode interface. Also, reference it as a field in the exception.
When throwing exceptions, simply pass in the appropriate enum.
1 |
throw new SystemException(PaymentCode.CREDIT_CARD_EXPIRED); |
Now when you need to test for a specific case, just compare the exception’s code with the enum.
1 2 3 4 5 |
} catch (SystemException e) { if (e.getErrorCode() == PaymentCode.CREDIT_CARD_EXPIRED) { ... } } |
By using the error code as the resource bundle’s lookup key, you can now get user-friendly, internationalized text!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class SystemExceptionExample3 { public static void main(String[] args) { System.out.println(getUserText(ValidationCode.VALUE_TOO_SHORT)); } public static String getUserText(ErrorCode errorCode) { if (errorCode == null) { return null; } String key = errorCode.getClass().getSimpleName() + "__" + errorCode; ResourceBundle bundle = ResourceBundle.getBundle("com.northconcepts.exception.example.exceptions"); return bundle.getString(key); } } |
3. Add error numbers to enums
In some cases, a numerical error code can be associated with each exception. HTTP responses are an example of this. For those cases, you can simply add a getNumber method to the ErrorCode interface and implement it in each enum.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public enum PaymentCode implements ErrorCode { SERVICE_TIMEOUT(101), CREDIT_CARD_EXPIRED(102), AMOUNT_TOO_HIGH(103), INSUFFICIENT_FUNDS(104); private final int number; private PaymentCode(int number) { this.number = number; } @Override public int getNumber() { return number; } } |
Numbering can be globally unique across all enums or each enum can be responsible for numbering itself. You can even use the implicit ordinal() method or load numbers from a file or database.
4. Add dynamic fields to your exceptions
Proper exception handling mandates for the recording of relevant data, not just the stack trace. This way, you will save time when trying to diagnose and reproduce errors. What’s more, customers won’t have to tell you what they were doing when your app crashed – you will already know and be on your way to fix it.
The easiest way to accomplish this is to add a java.util.Map field to the exception. The new field’s job will be to hold all your exception-related data by name.
You’ll also need to add a generic setter method following the fluent interface pattern.
Throwing exceptions, with relevant data, will now look something like this.
1 2 3 4 |
throw new SystemException(ValidationCode.VALUE_TOO_SHORT) .set("field", field) .set("value", value) .set("min-length", MIN_LENGTH); |
5. Prevent unnecessary nesting
Long, redundant stack traces not only do not help you, but they are also a waste of time and resources. When rethrowing exceptions, call a static wrap method instead of the exception’s constructor. The wrap method decides when to nest exceptions and when to just return the original instance.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public static SystemException wrap(Throwable exception, ErrorCode errorCode) { if (exception instanceof SystemException) { SystemException se = (SystemException)exception; if (errorCode != null && errorCode != se.getErrorCode()) { return new SystemException(exception.getMessage(), exception, errorCode); } return se; } else { return new SystemException(exception.getMessage(), exception, errorCode); } } public static SystemException wrap(Throwable exception) { return wrap(exception, null); } |
Your new code for rethrowing exceptions:
1 2 3 |
} catch (IOException e) { throw SystemException.wrap(e).set("fileName", fileName); } |
6. Use a central logger with a web dashboard
This is the bonus tip. Depending on your setup, accessing production logs can take some work, since it may require involving multiple go-betweens (since many devs may not have access to production environments).
If you are in a multi-server environment, things are even worse. Finding the right server — or determining that the problem only affects one server — can be quite a headache.
Here are my suggestions:
- Aggregate your logs in a single place, preferably a database.
- Make that database accessible from a web browser.
There are a number of methods and products to do this: log collectors, remote loggers, JMX agents, system monitoring software, etc. You can even build it yourself. Once you have it, you’ll be able to:
- Troubleshoot issues in a matter of seconds.
- Have a URL for each exception that you can bookmark or email around.
- Enable your support staff to determine root causes without involving you.
- Prevent testers from creating multiple tickets for the same bug. Plus they’ll have an exception URL to put in their ticket.
- Save money for your business.
And last but not least
- Keep your weekend and reputation intact.
What are your Tips?
I hope you find my tips useful. I have avoided many disasters and wasted hours by having the right info in my exceptions and having them easily accessible. If you have a few exception handling tips of your own, I’d like to hear them.
Download
The exceptions download contains the entire source code (including Eclipse project). The source code is licensed under the terms of the Apache License, Version 2.0.
Happy coding!
Nice & clean :). Its a concrete approach!
Very well thought out. I like it.
Lovely approach and will put it to use right away!
There’s a good chapter in Effective Java on this topic. One of the chapters also discusses ”
tagged classes”. Otherwise, good work getting more discussion going in better exception handling!
Ouch, I guess SystemException could be considered a “tagged class”.
In its defense, there’re no tag-specific behaviour in this class, like “switch” or “if” statements. And the errorCode field is an interface, not a direct reference to any enum.
Thanks for the comments and the Effective Java mention.
Excellent approach! I’ve been struggling with how to build a simple but usable REST exception handling process complete with logging to a mongodb for production use. I think this may just fit the bill nicely.
Thanks Kevin. I hope we get to hear more about your exception handling, REST service.
This is a good approach although I think one exception class might be a bit far, you could consider creating a base class and then sub exception classes with different Enums. This would allow you to have componentised development but still use the same base mechanism for handling errors.
Also a very minor point and I’m sure just used for an example but CREDIT_CARD_EXPIRED is not really an exception as this is an expected and predictable state for a credit card to be in and should be handled by the “Main Path” of your program.
This is my opinion, of course, but Exceptions should only be used for Exceptional circumstances (see a dictionary definition) otherwise you can end up throwing exceptions up several layers of your application because it is easy or using exception for logical flows.
Owen — you’re right that a single exception class is taking things far. Probably too far for most Java developers to stomach. For the skeptical, I say try to keep an open mind. I’ve had a surprising amount of success with the single exception approach over the years.
Having said that, I do like your recommendation. It’s a good middle ground. Most developers can accept it and it probably won’t break any of my production code 🙂 As long as you’re recommending a SystemException subclass per subsystem (accounts, inventory, tickets) and not the class-per-error we see today, then I’m on board.
Re: CREDIT_CARD_EXPIRED. IMO there’s a fine line between the main, alternate, and exceptional paths. (Is FileNotFoundException really an exceptional case???) I do agree with you that CREDIT_CARD_EXPIRED is questionable, like INVALID_USERNAME_OR_PASSWORD for example. A better example might have been something like UNSUPPORTED_CARD. Thanks for pointing this out.
My framework does something pretty much similar but the part that translate the messages needs to be an interface because does not know anything about specifics enums. So, I have an error service which is initialized in the framework by the app code where it ‘mount’ all error codes to be translated in the future.
Great article summarizing the technique ! congrats
Nice approach. I like your decoupling of user messages from exceptions/error codes. This also let’s you mount the appropriate messages for the current user role. Internal agents might see a different message than partners or customers for the same exception.
Thanks, I’ll be adding this to my arsenal.
Pingback: 6 porad jak ulepszyć obsługę wyjątków
#1 and #4 together make refactoring more difficult than necessary. Subclasses with dedicated getters and setters provide comile-time safety instead.
For a meaningful I18N handling, there should be a method returning all fields as an object array. That would allow for using MessageFormat.
The luxury version would include a facility to translate properties before using them to the MessageFormat. E.g., the text associated with an HTTP error code has to be translated in the first step before being included in a localized error message in the second step.
Sub classes would make #3 simpler as well with generics being used to declare, which error code class will be returned from a particular sub class.
How do I know which map keys I can use?
Example:
try {
someService.doSmth(); // here I don't know which implementation I call. Also, I didn't write that implementation.
} catch (SystemException e) {
if (e.getErrorCode() == PaymentCode.CREDIT_CARD_EXPIRED) {
//now what?
}
}
Ted,
You can get a list of the map’s keys dynamically using e.getProperties().keySet().
Pingback: » Enlaces de interés. 3era Búsqueda
well ,I will put it to use the project
Interesting article. However, I don’t agree about TIP #1. Doing this makes it impossible to use the Java language catch clause to selectively catch or ignore errors (you have to call methods on the exception to select exceptions and possibly rethrow). A better idea is to make the single exception type a base class and subtype that. This would let you define subtypes like FatalException whose handling can easily be customized. With this approach you can still use the other tips. Remember: one of the strength of Java is that it is a typed language. Not using types in Java is almost always an error.
Very cool. I like this way
SystemException.wrap(e).set('key', value)
But I have one more question. Is there any way to output the log? You know we always need to support production but always can not have the permission to get the log directly. is it exist library to can to output log to web page with filter(like password hidden)? Thanks
Thanks for the kudos Nimysan.
I’ve read about a few log aggregators and cloud loggers, but haven’t personally used any. You can google “log servers” or “log management” to see a few.
My approach in the past has been to:
1. Filter out passwords in the SystemException.set method by excluding calls with the keys password, pwd, or passwd.
2. Log all exceptions to database.
3. Add a simple exception viewer to the website’s admin page.
Hope that helps.
I’ve got a background in languages that don’t have exceptions and am trying to learn.
I remain unconvinced that I should ignore the classes-as-types and use enums as types.
Aren’t subclasses easier to maintain?
good approach!!
I get two errors when trying to use this code.
1. public enum WSCode implements ErrorCode
error: Interface Expected Here
2. public class ErrorCode {
int getNumber();
}
error: Missing method body or declare abstract
Any ideas; help!
ErrorCode is defined as an interface in the zip file, not sure why you’re seeing a class.
public interface ErrorCode {
int getNumber();
}
Try changing your code to match the above, that should fix both issues.
Dele
No such luck 🙁
For the line: public enum DataAccessCode implements ErrorCode {
I get the error: java: interface expected here
I get an error for @Override
java: method does not override or implement a method from a supertype
John,
I’m guessing for some reason you changed ErrorCode.java from an interface to a class. Unless you change it back to an interface, you’re going to continue getting these errors 🙁
Try re-downloading the zip from https://104.131.41.115/blog-downloads/exceptions/NorthConcepts-Exceptions.zip and confirm everything compiles before starting your changes.
Good luck,
Dele
Why your errorCode is an interface and why not class?
I wanted each category of errorCodes to be represented by a different enum type. Since enums can implement interfaces, but not extend classes, that’s what I used.
I did same (with little variations) many years back and still using, so that we can automate error alerts. It gave many other benefits too.
we had following additions:
ErrorCategory (e.g. config, network, DB, Retryable)
ErrorSverity (e.g. fatal, error, warn) in error codes.
Overridden getMessage() for standard error message, which helped us parsing error logs.
Global exception handler which looked at error properties and acted accordingly.
Later this approach helped us in internationalisation too.
I like your dynamic fields in exception, hope I’m allowed to borrow it 🙂
Thanks for sharing.
Hey Toqeer,
Thanks for your comment and info. It’s good to hear that others have independently thought of these ideas.
Yes, please use the dynamic fields in your own apps. Maybe one day something like this will get added to the JDK 🙂
Cheers.
Pingback: How to read data in parallel using AsyncMultiReader - Data Pipeline
Pingback: How To Throw customized Block Exception in Java - codeengine
Hi Dele,
Thanks for the wonderful post, very useful. so much ready for production
cheers
Prash
Pingback: 【转】改善异常处理的6个技巧 - 宇托的狗窝
Excellent article describing how to implement the exceptions in an optimized manner. Great tips.
I don’t agree with ” Use a single, system-wide exception”. IMHO I think that a more realistic approach would be “Use a single, module-wide exception”. But a like this article a lot. Congrats.
I have been working on centralizing the Excepting Handling since legacy days. This article describes a perfect methodology, My experience thru 40 years of architecture (a model with technology agnostic building blocks of a solution) converges having a separate technical service (to be custom tailored to a specific system) as the process and workflow are pretty much similar in all the technical and business components of the architecture. In fact, we can workout for a standard that could be inter-operable among the systems across the enterprise. This article certainly is a start in this direction. I would recommend that we should work with OASIS to promote these concepts to standardize.
Pingback: Exception handling | hfontis
I’d have to say I partially disagree. Described tips are really good when dealing with service exceptions which have to be the response when something wrong happens in the back-end. These are gonna be the nice, detailed – and thus many in numbers – exceptions that will gently tell the customer what just happened. As said, this includes l10n and proper HTTP code handling, therefore a good pattern is to use enums and codes. BUT, when it comes down to exceptions in back-end applications that are not used as client API, a daemon for example, this whole article is of no use. Then you are the person who’s the target of the exceptions, via the logs and the exception handling composition. IMHO, business defined exceptions in combination with nested exceptions give quite more details, preserving the logs from stack-floods (in case one’s using containers like JBoss). In few lines you see exception propagation and causes with their meaningful messages so you can recreate the chain of events straight away in your mind. Just an opinion guys, have a productive day! 😉