In this tutorial, we’ll learn about Java exceptions, why they occur, and how to handle them. Additionally, we’ll learn how to create custom exceptions.
2. Exceptions
The exceptions represent a problem that happens during program execution. If we don’t manage exceptions, our program will terminate. On the other hand, good exception handling can help our program to recover and continue with work.
Suppose we’d like to divide two numerical values. Let’s create a method that divides two numbers and returns the result:
public int divide(int firstNumber, int secondNumber) { return firstNumber/secondNumber; }
Now, let’s test our method:
int result = divide(10, 5);
As a result, we’ll get the 2 as a result.
During division, we should keep in mind one rule: “It’t illegal to divide with 0.”.
Now, Let’s call our method with the number 0 as a second argument:
int result = divide(10, 0);
Here, we made a mistake. Java will tell us that we made a mistake by throwing ArtihmeticException.
Program execution can result with an exception, especially if it depends on external factors such as internet connection, user input, and database connection.
For instance, if we try to connect to the web application without the internet connection, we’d get:
However, exceptions might not be so obvious at the time. For instance, let’s check the following example.
List<String> animals = List.of("lion", "zebra", "crocodile", "giraffe", "elephant"); String animal = animals.get(10); System.out.println(animal);
Here, we try to access the element in a collection that doesn’t exist. Java will throw IndexOutOfBoudsException.
4. Exception Types
Every exception type extends (directly or indirectly) the java.lang.Throwable class:
We distinguish three types of exceptions:
- Unchecked exceptions
- Checked exceptions
- Errors
4.1. Unchecked Exceptions
Unchecked exceptions represent all exceptions that extend java.lang.RuntimeException class.
They can be thrown by the developer or JVM. Additionally, we don’t need to handle them or declare them.
Now, let’s see the most used unchecked exceptions from the Java API:
Unchecked Exception |
Description |
ArithmeticException |
JVM throws an ArtihmeticException exception if we try to divide by zero. |
ArrayIndexOutOfBoundsException |
JVM throws an ArrayIndexOutOfBoundsException exception if we try to access an element by the unexisting index inside a collection. |
ClassCastException |
JVM throws ClassCastException if we perform a cast operator on two unrelated data types. |
IllegalArgumentException |
The developer throws an IllegalArgumentException to indicate the argument passed to a method is illegal. |
NullPointerException |
The JVM throws NullPointerException if we try to access members of an object that doesn’t exist (reference point to a null). |
NumberFormatException |
The developer throws NumberFormatException when we try to transform a String into a numerical value, but the String value doesn’t contain the correct format. For instance, “1g4k” can’t be transformed into a numerical value.
Additionally, this class is a subclass of an IllegalArgumentException class. |
4.2. Checked Exceptions
Checked exceptions represent all exceptions that extend java.lang.Exception class.
They can be thrown by the developer or JVM. Unlike unchecked exceptions, we need to handle or declare them.
Now, let’s see the most used checked exceptions from the Java API:
Checked Exception |
Description |
IOException |
The program throws an IOException if there is a problem with reading from or writing to a file. |
FileNotFoundException |
The program throws FileNotFoundException if we try to access a file that doesn’t exist.
Additionally, this class is a subclass of an IOException class. |
4.3. Errors
Error exceptions represent all exceptions that extend java.lang.Error class. It’s important to note we should never handle these exceptions.
Error |
Description |
StackOverflowError |
JVM throws StackOverflowError when a method calls itself too many times (infinite recursion). |
NoClassDefFoundError |
JVM throws NoClassDefFoundError when the class exists during the compile time but doesn’t exist during runtime. |
5. Custom Exceptions
Furthermore, we aren’t limited to the exception types Java API provides. We can create our own custom exceptions.
5.1. Unchecked Exceptions
Now, let’s see how to create a custom unchecked exception. As already mentioned, unchecked exceptions extend RuntimeException class.
To do so, firstly, our class needs to extend RuntimeException class:
public class NoMoreFoodException extends RuntimeException { public NoMoreFoodException() { } public NoMoreFoodException(String message) { super(message); } public NoMoreFoodException(String message, Throwable cause) { super(message, cause); } }
The RuntimeException has five constructors we can use in our custom class:
- No-args constructor
- Constructor with a String parameter that represents an error message
- Constructor with String and Throwable parameters (if we’d like to pass the original exception)
- Constructor with a Throwable parameter
- Constructor with four parameters: message, Throwable, a boolean parameter that indicates termination, and another boolean parameter that includes a stack trace.
It’s not mandatory to include all five constructors in our class. Usually, we’d only want to include the ones we’ll actually use.
5.2. Checked Exceptions
Let’s examine how to create the checked exceptions.
Firstly, our class needs to extend the Exception class:
public class DuplicateValueException extends Exception { public DuplicateValueException() { } public DuplicateValueException(String message) { super(message); } }
The Exception class contains the same five constructors as RuntimeException.
6. Thworing Exceptions
Let’s see how we can tell Java to throw a specific exception.
We can throw an exception using the throw keyword. After the keyword, we need to create a new object of a class representing an exception:
throw new Exception(); throw new RuntimeException(); throw new NoMoreFoodException(); throw new NoMoreMeatException();
6.1. Unchecked Exceptions
Furthermore, let’s see the example that throws our NoMoreFoodException:
int foodQuantity = 5; while (true) { foodQuantity--; if (foodQuantity == 0) { throw new NoMoreFoodException("No more food on the stock."); } }
In the code snippet above, we’ll throw NoMoreFoodException when the foodQuantity reaches 0. Since we’re not handling the exception in any way, our program will terminate.
6.2. Checked Exceptions
Likewise, we can throw a checked exception. However, there is one difference. When throwing a checked exception, we should indicate our method could throw such an exception in a method declaration. We should use the throws keyword within the exception type.
private static void checkForDuplicates(String[] values) throws DuplicateValueException { String[] noDuplicates = new String[values.length]; for(int i = 0; i < values.length; i++ ){ String value = values[i]; for (String noDuplicate : noDuplicates) { if (value.equals(noDuplicate)) { throw new DuplicateValueException("Duplicate value found"); } } noDuplicates[i] = value; } }
Additionally, using the throws keyword, we indicate our method doesn’t handle DuplicateValueException. Moreover, we transfer the responsibility for handling the exception to the caller’s method.
7. Catching Exceptions
Simply put, by catching exceptions, we prevent exceptions from terminating our program. Therefore, we try to recover our program so it can continue with the normal workflow.
However, it’s important to note we shouldn’t try to recover from all exception types. For instance, we should never try to continue program execution after Error exceptions.
For example, let’s get back to our divide method. We know we’re not allowed to divide by zero. However, the user can still enter the number 0 as a divisor, which will cause an ArithmeticException. In this case, instead of terminating our program, we could handle the exception and ask the user to re-enter the numbers.
7.1. Try-catch block
We use a try block to exclude the part of the code that can throw an exception.
The code placed inside the try block executes normally like any other code.
Additionally, we use a catch block to catch specific exception types:
int foodQuantity = 5; try { while (true) { foodQuantity--; if (foodQuantity == 0) { throw new NoMoreFoodException("No more food on the stock."); } } }catch (NoMoreFoodException e){ System.err.println("An error occurred!"); }
The curly brackets are mandatory for both try and catch blocks.
Furthermore, if a code placed inside the try block causes an exception, the execution flow will transfer to the catch block. To transfer the execution flow, our catch blook needs to catch the exception that is thrown by the try block. Consequently, if the try block throws an exception we don’t catch, the catch block will never execute.
One try block can have multiple catch blocks:
try { // omitted for clarity } catch (NoMoreFoodException e) { System.err.println("An error occurred!"); } catch (Exception e) { System.err.println("Something went wrong."); }
Lastly, try can’t stand by itself. We need to have either catch or finally block.
7.2. Finally blok
Now, the code placed in the finally block will always execute, regardless of the exception.
We could use the finally block when we’d like some action to happen even if the exception is thrown.
For instance, if we’re working with a database, we’d like to close the connection always, even if an exception occurs. Here, we’d put the closing connection inside the finally block.
try { // omitted for clarity } catch (NoMoreFoodException e) { System.err.println("An error occurred!"); } catch (Exception e) { System.err.println("Something went wrong."); }finally { System.out.println("Notify administrator about quantity"); norify(); }
8. Key Takeaways
In this tutorial, we learned about exceptions in Java.
To sum up, exceptions occur to indicate something is wrong with our code. We can create and use our own exceptions to prevent users from entering undesired values that will terminate our program.