Java Exceptions

Introduction

Java is a powerful and versatile programming language, but like any software, it’s not immune to errors and unexpected situations. In Java, exceptions are a mechanism that allows developers to handle such situations gracefully.

What is an Exception?

An exception is an event, which occurs during the execution of a program, that disrupts the normal flow of the program’s instructions.

When an error occurs within a method, the method creates an object and hands it off to the runtime system. The object, called an exception object, contains information about the error, including its type and the state of the program when the error occurred. Creating an exception object and handing it to the runtime system is called throwing an exception.

 

After a method throws an exception, the runtime system attempts to find something to handle it. The set of possible “somethings” to handle the exception is the ordered list of methods that had been called to get to the method where the error occurred. The list of methods is known as the call stack as shown below:

The runtime system scans the call stack to locate a method containing a code block capable of managing the exception; this block is referred to as an exception handler. The search initiates from the method where the error originated and progresses through the call stack in the reverse order of method calls. Once a suitable handler is identified, the runtime system transfers the exception to that handler. A handler is deemed appropriate if the type of the thrown exception object matches the type manageable by the handler.

The chosen exception handler is said to catch the exception. If the runtime system exhaustively examines all methods on the call stack without discovering a fitting exception handler, leading to a scenario depicted in the subsequent illustration, the runtime system (and consequently, the program) concludes abruptly.

Types of Exceptions

There are three main types of exceptions in Java: checked exceptions, unchecked exceptions, and errors.

1. Checked exceptions are exceptions that are declared in the throws clause of a method declaration. The compiler checks whether the method properly handles the potential exceptions or whether the calling method is prepared to handle them. If a checked exception is thrown from a method that does not declare it, the calling method will be terminated with an error.

Common checked exceptions include:

  1. IOException: Thrown when an I/O error occurs, such as reading or writing to a file.
  2. ClassNotFoundException: Thrown when a class cannot be loaded from the classpath.
  3. SQLException: Thrown when an error occurs while accessing a database.

2. Unchecked exceptions, known as RuntimeException is a special case: it’s unchecked (i.e. it need not be declared by a method and the compiler doesn’t force you to catch it). The compiler does not check for unchecked exceptions, and they can be thrown from any method, regardless of whether it declares them or not.

Common unchecked exceptions include:

  1. NullPointerException: Thrown when a null reference is used to access an object’s member.
  2. ArithmeticException: Thrown when an arithmetic operation is performed on a value that is not of the correct type or is outside the valid range.
  3. ArrayIndexOutOfBoundsException: Thrown when an index is used to access an element of an array that is outside the array’s bounds.

3. Errors is the “rare” case: it signifies problems that are outside the control of the usual application: JVM errors, out of memory, problems verifying bytecode: these are things that you should not handle because if they occur things are already so bad that your code is unlikely to be able to handle it sanely.

Common errors include:

  1. OutOfMemoryError: In case JVM cannot allocate an object as it is out of memory, such error is thrown that says no more memory could be made available by the GC.
  2. NoClassDefFoundError: If a class loader instance or JVM, try to load in the class definition and not found any class definition of the class.
  3. StackOverflowError: When a stack overflow occurs in an application because it has recursed too deeply.
  4. VirtualMachineError: Indicate that the JVM is broken or has run out of resources, essential for continuing operating.

 

Exception class hierarchy in Java

The Throwable class is the superclass of all exceptions and errors in Java. All other exception classes are subclasses of the Throwable. The exception class hierarchy in Java is as follows:

 

Example Of Checked Exception

  1. FileNotFoundException: This exception is thrown when a file doesn’t exist or cannot be opened for reading or writing.
				
					try {
    FileInputStream fileInputStream = new FileInputStream("file.txt");
} catch (FileNotFoundException e) {
    System.err.println("File not found: " + e.getMessage());
}

				
			
  1. IOException: This exception is a general class for I/O errors, encompassing a wide range of possible issues such as network errors, data corruption, and communication problems.
				
					try {
    BufferedReader reader = new BufferedReader(new FileReader("data.txt"));
    while (!reader.ready()) {
        System.out.println("Waiting for data...");
    }
    String line = reader.readLine();
    System.out.println("Read line: " + line);
} catch (IOException e) {
    System.err.println("Error reading file: " + e.getMessage());
}

				
			
  1. SQLException: This exception is thrown when there is a problem interacting with a database. It encompasses errors such as invalid SQL statements, connection issues, and data integrity violations.
				
					try {
    Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase");
    Statement statement = connection.createStatement();
    statement.executeUpdate("UPDATE customers SET name = 'John Doe' WHERE id = 1");
} catch (SQLException e) {
    System.err.println("Error executing SQL statement: " + e.getMessage());
}

				
			

4. ParseException: This exception is thrown when there is an error parsing a string into a specific format. It is commonly encountered in parsers for XML, JSON, or other structured data formats.

				
					try {
    JSONParser parser = new JSONParser();
    JSONObject object = (JSONObject) parser.parse(new FileReader("data.json"));
    String name = object.getString("name");
    System.out.println("Name: " + name);
} catch (ParseException e) {
    System.err.println("Error parsing JSON: " + e.getMessage());
}

				
			

Example Of UnChecked Exception

  1. NullPointerException: This exception is thrown when an object reference is dereferenced and the object is null. This can occur due to programming errors, such as attempting to call a method on an uninitialized object or accessing a member of an object that doesn’t exist.
				
					String text = null;
System.out.println(text.length()); // NullPointerException

				
			
  1. ArrayIndexOutOfBoundsException: This exception is thrown when an attempt is made to access an array element outside the valid index range. This can occur due to programming errors, such as indexing an array beyond its length or using an invalid index value.
				
					int[] numbers = new int[5];
System.out.println(numbers[10]); // ArrayIndexOutOfBoundsException

				
			

3. ArithmeticException: This exception is thrown when an arithmetic operation results in an invalid value. Common examples include dividing by zero or performing modulo division with a negative divisor.

				
					int result = 10 / 0; // ArithmeticException

				
			

Example Of Error

OutOfMemoryError: This error occurs when the Java Virtual Machine (JVM) runs out of memory. This can happen due to excessive memory allocation or resource-intensive operations.

				
					for (int i = 0; i < Integer.MAX_VALUE; i++) {
  new Object(); // Excessive object creation leads to OutOfMemoryError
}

				
			

A Custom Or User-Defined Exception

Custom exceptions, also known as user-defined exceptions, are classes that extend the Exception or RuntimeException class. They are used to handle specific errors or situations that are not covered by the standard Java exceptions.

Creating a Custom Exception

To create a custom exception, follow these steps:

  1. Define the Class: Create a class that extends either Exception or RuntimeException. Exception is a checked exception, which means that the code that throws it must be handled explicitly. RuntimeException is an unchecked exception, which means that the code that throws it does not have to be handled explicitly.
  2. Declare constructor: Declare a constructor for your custom exception. The constructor should take the necessary parameters to capture information about the error. For example, you can include a message describing the error, a stack trace, or any other relevant data.

  3. Implement Custom Methods: Implement methods to provide additional functionality for the exception. For example, you could add a method to get the error message or to convert the exception to a string.
Example: In this case, I’ll create a simple banking application where we define a custom exception called InsufficientFundsException to handle situations where an account balance goes below a certain threshold.
				
					// Custom exception class
class InsufficientFundsException extends Exception {
    public InsufficientFundsException() {
        super("Insufficient funds in the account.");
    }

    public InsufficientFundsException(String message) {
        super(message);
    }
}

// Bank Account class
class BankAccount {
    private String accountHolder;
    private double balance;

    public BankAccount(String accountHolder, double initialBalance) {
        this.accountHolder = accountHolder;
        this.balance = initialBalance;
    }

    public void withdraw(double amount) throws InsufficientFundsException {
        if (amount > balance) {
            throw new InsufficientFundsException();
        }
        balance -= amount;
        System.out.println("Withdrawal successful. Remaining balance: " + balance);
    }
}

// Example of using the custom exception
public class BankingApplication {
    public static void main(String[] args) {
        BankAccount account = new BankAccount("John Doe", 1000);

        try {
            account.withdraw(500);
            account.withdraw(800); // This will throw InsufficientFundsException
        } catch (InsufficientFundsException e) {
            System.out.println("Caught an exception: " + e.getMessage());
        }
    }
}

				
			

In this example: The InsufficientFundsException class extends the Exception class and provides a default constructor with a predefined error message.

The BankAccount class has a withdraw method that simulates a withdrawal from the account. If the withdrawal amount exceeds the account balance, it throws the InsufficientFundsException.

In the main method, we create a BankAccount instance with an initial balance of $1000. We then attempt to withdraw $500 (successful) and $800 (which will throw InsufficientFundsException). The exception is caught, and an appropriate message is printed.