Best (and Worst) Java Exception Handling Practices

 Handling Exceptions in Java is one of the fundamental things a developer should be experienced in - handling them is as important as the rest of the code, and should never be overlooked or underestimated.

In this blog, I'll try my best to make a guide to help newer developers understand how to handle their exceptions according to industry standards, and also how not to handle them.

I'm assuming you're already familiar with what exceptions are, and what exception handling is as well as basic rules like the handle-or-declare rule. That being said, I won't cover any of that information and jump straight to practices, aimed at newer developers who mainly have experience with textbook examples.

Best exception handling practices

Use try-with-resources

Java 7 introduced us with a new way to handle resources in try-catch blocks.
By adding this change, the older, much more verbose, try-catch-finally blocks can be substituted with a cleaner and simplified version:

static String readFirstLineFromFile(String path) throws IOException {
    try(BufferedReader br = new BufferedReader(new FileReader(path))) {
        return br.readLine();
    }
}

Evidently, the declaration appears within the parentheses of the try block. 

You can also include multiple resources, one after another:

static String multipleResources(String path) throws IOException {
    try(BufferedReader br = new BufferedReader(new FileReader(path));
        BufferedWriter writer = new BufferedWriter(path, charset)) {
        //some code
    }
}


Using try-with-resources ensures that all resources included will be closed upon the end of the statement.

Close resources in try-catch-finally

If, for any reason, you decline to use the above-mentioned block for dealing with resources, make sure to close them yourself in the finally block.

public String readFile(String path) throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));   
    try {
        return br.readLine();
    } finally {
        if(br != null) br.close();
    }
}


Be specific with exceptions

When a method can throw multiple exceptions, which isn't rare, there is a specific order which you need to follow when catching them - most specific to most generic.

Java requires you to catch from most specific to most generic, and failing to do so will result in a compiler error:

public void parseFile(String filePath) {
    try {
        //some code 
    } catch (FileNotFoundException ex) {
        //handle
    } catch (IOException ex) {
        //handle
    }
}

If the method incurs an exception, the JVM firstly checks whether FileNotFoundException is an appropriate one. If not, it checks the IOException. Catching the more generic exception also catches the more specific ones as well.

Avoid exceptional conditions

In some cases, you can avoid certain runtime exceptions with simple checks, which helps you alter the flow of your code without encountering exceptional conditions.

public void getEmployee(int i) {
    String[] employeeArray = {"David", "Rhett", "Andrew"};
    
    if(i >= employeeArray.length) {
        System.out.println("Index is too high!");
    } else {
        System.out.println(employeeArray[i]);
    }
}

If the index of the array member we are trying to access is too high, we would usually get an ArrayIndexOutOfBoundsException. This way, if the index of the array member we are trying to access is too high, the offending line of code will not execute, and we're prompted with a message to lower the index - thus, no exception arises.

Worst exception handling practices

Swallowing exceptions

As far as the compiler is concerned, it's enough to just catch an exception. You don't really have to do anything about it.
Swallowing an exception refers to the act of catching an exception and then doing nothing to fix the issue:

public void parseFile(String filePath) {
    try {
        // some code that forms an exception
    } catch (Exception ex) {}

}

This is valid, the compiler doesn't complain but what this code does is very bad.  While the exception is caught, we do nothing to fix the arising issue and any kind of useful information we could extract from the caught exception is lost. 

Another common practice is to simply print out the stack trace:

public void parseFile(String filePath) {
    try {
        //some code that forms an exception
    } catch(Exception ex) {
        ex.printStackTrace();
    }
}

...

While this is better than ignoring it, by printing out the stack trace and allowing us to locate and deal with the offending code, this doesn't directly improve the code and doesn't fix the issue. That's why this is also considered swallowing an exception.

return in a finally block

According to the JLS (Java Language Specification):

If execution of the try block completes abruptly for any other reason R, then the finally block is executed, and then there is a choice.
If the finally block completes normally, then the try statement completes abruptly for reason R.
If the finally block completes abruptly for reason S, then the try statement completes abruptly for reason S (and reason R is discarded).

If this is by any means confusing, here's a simplified version:

By abruptly returning from a finally block, the JVM will drop the exception from the try block and all valuable data will be lost.

Here's how it might look like:

public void parseFile(String filePath) {
    String name = "David";
    try {
        throw new IOException();
    } finally {
        return name;
    }
}

In this example, even though our try block throws an IOException, the exception itself is dropped by the JVM because the finally block completes abruptly. This causes the try block to also end abruptly for the same reason and the IOException becomes irrelevant.

throw in a finally block

Another way to drop an exception and lose all valuable information from it is to use a throw in a finally block.

The exception thrown from the finally block will overshadow the exception from the catch block, thus rendering it irrelevant for the JVM:

public void parseFile(String filePath) {
    try {
        // some code that forms an exception
    }catch(IOException io) {
        throw new IllegalStateException(io);
    }finally {
        throw new MyException();
    }
}

throw as a goto statement

While finding creative ways to overcome something is a good trait for programmers, solutions should also be effective.

Java doesn't have a goto statement like some other languages, and some people who are used to using goto statements started throwing exceptions to simulate them.

public void someMethod() {
    try {
      //some code 1
      throw new MyException();
      //some code 2
    } catch(MyException ex) {
      //some code 3
    }
}

The end goal of this code is to skip some code 2. This is ineffective and slow due to exceptions being designed for exceptional code, not regular program logic flow. Java offers more than enough flow control logic to do just about anything, so simulating a goto statement is not the best idea.

Catching Exception

A natural question then is "If we need to catch from most specific to most generic, catching Exception alone would catch all preceding sub-classes as well, so why not just use it alone?"

This, however, is considered bad practice. Catching Exception itself disallows you to handle the arising exception properly, which is one of the main reasons exception handling exists. Additionally, catching Exception will also catch all RuntimeExceptions.

Catching Throwable

Similar to catching the most generic possible exception, catching Throwable, which is a superclass to all exceptions and errors, is pointless and dangerous.

Catching Throwable will catch all Errors and Exceptions. This is very bad since errors are thrown by the JVM only if the application encounters a serious condition such as StackOverflowError or OutOfMemoryError and they are never meant to be handled.

Logging and throwing

When trying to uncover the reason your application is not working as intended, don't both log and throw an exception.

public void parseFile(String filePath) throws IOException {
    try {
        //code that forms an exception
    } catch(IOException ex) {
        LOGGER.error("IOException: ", ex);
        throw ex;
    }
}

Doing both is redundant and will result in multiple log messages which will simply clog the useful information with the amount of text.

Conclusion

We've covered 13 best and worst exception handling practices in Java. Sometimes, it seems that exceptions are overlooked, which can cause problems and should be addressed by shedding some light on exception handling.

Hopefully, this blog helped you notice a bad habit in time if there were any bad habits!