Error handling in Java
Published: 2020-03-20 10:27:11

Compiled languages are very needy, but at least you know before you deploy the code that you've accounted for most edge cases. However, this definitely comes under 'don't just do what the IDE suggests', which has caused me problems before now, so let's establish some appropriate error handling.

**What's to be done**

Certain methods are more likely to fail than others, so to compile you have to use error-handling; in this case either `try/catch` or `throws` will get the job done.

**Example 1**

    public class Database {
        Connection connection;
        public Database(String databaseUrl) {
            Class.forName(org.postgresql.Driver);
            connection = DriverManager.getConnection(databaseUrl, "postgresUser", "postgresPassword");
        }
    }

Attempting to connect to a Postgresql database above will cause two compiler problems:
- forName() Unhandled exception: java.lang.ClassNotFoundException
- getConnection() Unhandled exception: java.sql.SQLException

These can be solved together or separately with the solutions I mentioned above.

**Try/catch**

    public class Database {
        Connection connection;
        public Database(String databaseUrl) {
            try {
                Class.forName(org.postgresql.Driver);
                connection = DriverManager.getConnection(databaseUrl, "postgresUser", "postgresPassword");
            } catch(ClassNotFoundException | SQLException e) { // or just (Exception e)
                e.printStackTrace();
            }
        }
    }

**Throws**

    public class Database throws ClassNotFoundException, SQLException { // or, again, just Exception
        Connection connection;
        public Database(String databaseUrl) {
            Class.forName(org.postgresql.Driver);
            connection = DriverManager.getConnection(databaseUrl, "postgresUser", "postgresPassword");
        }
    }

**But which! When?**

Let's quickly go over why this happens. The reason we need to handle anything is that there is a chance that one of the methods we're using will call `throws`. The `forName()` method comes with `throws` built in, so we either have to say that the method in which we use it (in this case, `Database`) could throw an error, or catch it.

If we choose `throws`, we're allowing the error to move on, and unless it is handled by another method - like a method that calls `Database` - it will cause the program to terminate with an error message.

However, when we use `try/catch` we're saying:
- Yes, this code is risky but `try` it anyway
- If it throws an error we'll `catch` it and do this instead. Usually 'do this instead' is an error message for the user.

And on top of `try/catch` is `finally`:
- Whatever happens, we will do this afterwards

**So...**

To condense this, `throw` passes responsibility up to the next method to be handled (or ends the program if not handled eventually), and catch handles exceptions in a meaningful way now.

**When to let it throw**

Tests are a good time. In JUnit, you can assert that a method throws an error in whatever case, and if you didn't plan for it to throw, then your tests won't pass and you'll find out quickly.

**Checked vs. Unchecked Exceptions**

So far, everything we've covered has been about checked exceptions, which are exceptions found by the compiler before the program is run. There are also unchecked exceptions or 'Runtime Exceptions', which are only found when the program is running.

**Checked Exception Examples**

    class Iterate {
        public static void main(String args[]) {
            String arr[] = {"Primo", "Two", "Third"}
            System.out.println(arr[10]);
        }
    }

The above will compile, but then throw an `IndexOutOfBoundsException` as we're trying to access the tenth element of an array with only three items.

Similarly, if you call an instance that hasn't been instantiated and get a `NullPointerException`, that would be an unchecked exception. These exceptions should still be handled, you just won't have them pointed out by the compiler.

**Summary**

Checked exceptions are picked up by the compiler and must be handled. They tend to involve interactions outside of the program (reading files, database queries, input).

Unchecked exceptions happen during runtime and should be handled, but won't prevent compilation. They are mostly problems within the program (null, type incompatibility, out-of bounds, dividing by zero).