We already handle Java custom exceptions in code in almost every one of our industry standard applications. A common approach is to create a custom exception class that semantically inherits the base Exception class.

1)Java custom exception handling – a new approach

1.1 Traditional exception handling

Our new approach uses static inner classes to handle each new exception scenario.

Traditionally we create a DBException by inheriting the Exception class. Then, each time we encounter a database-related exception that needs to be thrown, we create an instance of DBException, add some information and throw it.

Now let’s consider the following scenario where we need to throw a DBException:

  1. SQL execution error
  2. Could not find any row of data
  3. We return multiple rows of data when we only need one
  4. Invalid parameter error
  5. Other errors

The problem with the above approach is that DBException does not provide enough information to handle the exception use cases listed above separately when these exceptions are handled in the catch block or in the application code.

1.2 New exception handling using inner classes

Let’s solve this problem by creating an inner class for each use case and then combining them inside DBException.

Start by creating an abstract BaseException to be the parent of all exception classes.

// BaseException.java
public abstract class BaseException extends Exception{
    private String message;
 
    public BaseException(String msg)
    {
        this.message = msg;
    }
    public String getMessage(a) {
        returnmessage; }}Copy the code

Now create our Exception inner class.

// DBExeption.java
public class DBExeption
{

    public static class BadExecution extends BaseException
    {
        private static final long serialVersionUID = 3555714415375055302L;
        public BadExecution(String msg) {
            super(msg); }}public static class NoData extends BaseException
    {
        private static final long serialVersionUID = 8777415230393628334L;
        public NoData(String msg) {
            super(msg); }}public static class MoreData extends BaseException
    {
        private static final long serialVersionUID = -3987707665150073980L;
        public MoreData(String msg) {
            super(msg); }}public static class InvalidParam extends BaseException
    {
        private static final long serialVersionUID = 4235225697094262603L;
        public InvalidParam(String msg) {
            super(msg); }}}Copy the code

Here we create a number of inner classes to handle each exception. You can extend new exception inner classes as you like.

1.3 How do I Use custom Exceptions

To understand what it does, let’s now create an exception and throw it. We will then see an error message in the log.

// TestExceptions.java
public class TestExceptions {
    public static void main(String[] args)
    {
        try
        {
            throw new DBExeption.NoData("No row found for id : x");
        }
        catch(Exception e) { e.printStackTrace(); }}}Copy the code

Program output:

Console
com.exception.DBExeption$NoData: No row found for id : x
at com.test.TestExceptions.main(TestExceptions.java:7)
Copy the code

As you can see in the log message in the exception stack, the information it carries is much more specific. It clearly shows what is wrong. In application code, you can also check for custom exception instances.

2. Advantages of using inner classes as custom exception classes

  1. The most obvious advantage is that even if other developers write some hard to read error messages, you can clearly figure out what the error is.
  2. You can use different exception instances to handle different exception scenarios
  3. You don’t need a single exception to cover many exceptions
  4. It’s easier to write negative unit test cases
  5. Logs are more meaningful and readable