Getting exception handling right can save you hours (or even days) of troubleshooting. Unexpected production issues can ruin your dinner and weekend plans. They can even affect your reputation if not resolved quickly. Having a clear policy on how to manage exceptions will save you time diagnosing, reproducing, and correcting issues. Here are 6 tips to improve your exception handling.



1. Use a single, system-wide exception class

Instead of creating separate classes for each exception type, create just one. And make it extend RuntimeException. This will reduce your class count and remove the need to declare exceptions you aren’t going to handle anyway.

I know what you’re thinking: How will I tell exceptions apart if they’re all the same type? And how will I track type-specific properties? Read on!

2. Use enums for error codes

Most of us were trained to put the cause of an exception into its message. This is fine when reviewing log files (ugh), but it does have drawbacks:

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.

A better approach is to use enums to indicate the exception’s type. Create one enum for each category of errors (payments, authentication, etc.). Make the enums implement an ErrorCode interface and reference it as a field in the exception.

When throwing exceptions, simply pass in the appropriate enum.

throw new SystemException(PaymentCode.CREDIT_CARD_EXPIRED); 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.

} catch (SystemException e) { if (e.getErrorCode() == PaymentCode.CREDIT_CARD_EXPIRED) { ... } } 1 2 3 4 5 } catch ( SystemException e ) { if ( e . getErrorCode ( ) == PaymentCode . CREDIT_CARD_EXPIRED ) { . . . } }

User-friendly, internationalized text can now be retrieved by using the error code as the resource bundle’s lookup key.

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); } } 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 for example. For those cases, add a getNumber method to the ErrorCode interface and implement it in each enum.

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; } } 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

Good exception handling means also recording relevant data, not just the stack trace. Doing this will save you big time when trying to diagnose and reproduce errors. And customers won’t have to tell you what they were doing when your app stopped working (you’ll already know and hopefully have fixed it).

The easiest way to do 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 the following.

throw new SystemException(ValidationCode.VALUE_TOO_SHORT) .set("field", field) .set("value", value) .set("min-length", MIN_LENGTH); 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 help no one. Even worse, they waste your time and resources. When rethrowing exceptions, call a static wrap method instead of the exception’s constructor . The wrap method will be responsible for deciding when to nest exceptions and when to just return the original instance.

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); } 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 & amp ; & amp ; 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 will look like the following.

} catch (IOException e) { throw SystemException.wrap(e).set("fileName", fileName); } 1 2 3 } catch ( IOException e ) { throw SystemException . wrap ( e ) . set ( "fileName" , fileName ) ; }

6. Use a central logger with a web dashboard

Consider this tip a bonus. Depending on your situation, getting access to production logs could be quite a hassle. A hassle that may involve multiple go-betweens (since many developers don’t have access to production environments).

Things get worse if you’re in a multi-server environment. Finding the right server — or determining that the problem only affects one server — can be quite a headache.

My recommendations are:

Aggregate your logs in a single place, preferably a database. Make that database accessible from a web browser.

There are many ways to do this and may products to choose from: log collectors, remote loggers, JMX agents, system monitoring software, etc. You can even build it yourself. The main thing is that you do it soon. 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.

Keep your weekend and reputation intact.

What are your Tips?

I hope you find these tips useful. Many disasters and wasted hours have been averted by having the right info in my exceptions and having them in an easy to access location. 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!