Page Content

Tutorials

How many types of Exception Handling are there in Java?

Exception Handling in Java

Exception handling in Java offers a well-organised and effective way to handle runtime faults and unusual circumstances, greatly increasing the robustness and dependability of programs.

What is an Exception?

An exception is an occurrence that breaks the regular flow of instructions while a program is running. When a Java method encounters such an unusual circumstance, it generates an exception object that contains details about the error, such as its kind and the state of the program at the moment of the occurrence. After that, the Java runtime system is “thrown” this object to process.

Consider a common scenario like division by zero:

public class ExceptionDemo {
    public static void main(String[] args) {
        System.out.println("Start");
        System.out.println("1");
        System.out.println("2");
        System.out.println("3");
        System.out.println(10 / 0); // This line will cause an exception
        System.out.println("4");    // This line will not be executed
        System.out.println("5");    // This line will not be executed
        System.out.println("End");      // This line will not be executed
    }
}

Output:

Start
1
2
3
Exception in thread "main" java.lang.ArithmeticException: / by zero
        at ExceptionDemo.main(ExceptionDemo.java:7)

The attempt to divide by zero in this instance is an improper arithmetic operation. After identifying this, the Java runtime system generates an ArithmeticException object and throws it. Since no code specifically handles this, the program is terminated by the default exception handler, which also publishes a stack trace that details the exception, its kind, and the location of the occurrence.

Benefits of Exception Handling

Java’s exception handling provides a number of significant benefits over conventional error-handling methods, such as returning error codes:

Benefits of Java Exception Handling
Benefits of Java Exception Handling
  • Separating Error-Handling Code: This enhances readability and maintainability by neatly separating error-handling code from “regular” program logic.
  • Propagating Errors Up the Call Stack: The ability to propagate exceptions up the chain of method calls until an appropriate handler is located is known as “propagating errors up the call stack.” By doing this, each method in the call chain can avoid manually checking for and passing on error codes.
  • Grouping and Differentiating Error Types: In order to enable specific handling for various problem types, it is possible to group similar error types through the exception class hierarchy and differentiate them.
  • Graceful Recovery: Instead of stopping suddenly in the event of an error, programs can react and carry on as usual. This is essential for apps that are easy to use and should not crash due to little input errors, for example.

Exception Hierarchy

Java’s exception classes are all a part of a hierarchy that starts with the java.lang.Class throwable. The two direct subclasses of this class are Exception and Error.

  1. java.lang.Throwable: This is the superclass of all error and exception types in Java. Its methods provide common functionalities for all exceptions, such as retrieving a detailed message or the stack trace.
    • Key methods include:
      • getMessage(): Returns a detailed message about the exception.
      • toString(): Returns the class name concatenated with the message. This method is often called when printing an exception object.
      • printStackTrace(): Prints the stack trace to the standard error stream, showing the sequence of method calls leading to the exception.
      • getCause(): Returns the underlying cause of this throwable or null if the cause is nonexistent or unknown.
      • fillInStackTrace(): Fills the stack trace of this Throwable object with the current stack trace.
  2. java.lang.Error: Usually unrecoverable by the application itself, these are serious issues that occur beyond of the user’s or programmer’s control. OutOfMemoryError, StackOverflowError, and UnknownError are a few examples. Programs generally do not catch Error types because recovery is usually not possible.
  3. java.lang.Exception: The regular exceptions that a well-written application should expect and manage fall under the superclass Exception. Program activity, including I/O errors or improper method parameters, is frequently the cause of these. Subclassing Exception is a typical way for developers to construct their own unique exception classes.
  4. java.lang.RuntimeException: A significant subclass of exception, runtime exceptions reflect common runtime error kinds that frequently point to programming problems. ArithmeticException (division by zero), NullPointerException (access to a null object), ArrayIndexOutOfBoundsException (invalid array index), and ClassCastException are a few examples.

Checked vs. Unchecked Exceptions

Java separates exceptions into two primary kinds, which affects how the compiler handles them:

  • Checked Exceptions:
    • It is reasonable to assume that a client software will recover from these predicted occurrences.
    • Usually, file not found, network problems, or SQL errors are the causes, which are not directly within the program’s control.
    • Checked exceptions are handled by the Java compiler. If a method can throw a checked exception, it must disclose it in its throws clause or catch it (using try-catch). If you don’t, a compile-time error will occur.
    • Common examples: IOException, SQLException, FileNotFoundException, ClassNotFoundException, InterruptedException, CloneNotSupportedException.
  • Unreported Exceptions:
    • These are usually unexpected occurrences or programming errors from which an application is typically unable to recover successfully.
    • They are RuntimeException subclasses.
    • The handling of unchecked exceptions is not enforced by the Java compiler. They don’t have to be declared in a throws clause or explicitly caught by methods. This is due to their nearly universal occurrence and the fact that rigorous programming techniques are frequently required to prevent them.
    • Common examples: ArithmeticException, NullPointerException, ArrayIndexOutOfBoundsException, IllegalArgumentException, NumberFormatException, ClassCastException.

Here’s an example demonstrating a custom checked exception and the use of the throws keyword:

// Custom checked exception class
class InsufficientFundsException extends Exception {
    private double amount;
    public InsufficientFundsException(double amount) {
        this.amount = amount;
    }
    public double getAmount() {
        return amount;
    }
    @Override
    public String toString() {
        return "InsufficientFundsException: You are short $" + amount;
    }
}
// Class that uses the custom exception
class CheckingAccount {
    private double balance;
    private int accountNumber;
    public CheckingAccount(int number) {
        this.accountNumber = number;
        this.balance = 0;
    }
    public void deposit(double amount) {
        balance += amount;
        System.out.println("Deposited $" + amount + ". New balance: $" + balance);
    }
    // Declares that this method might throw InsufficientFundsException
    public void withdraw(double amount) throws InsufficientFundsException {
        if (amount <= balance) {
            balance -= amount;
            System.out.println("Withdrew $" + amount + ". New balance: $" + balance);
        } else {
            double needs = amount - balance;
            // Throws a new instance of the custom exception
            throw new InsufficientFundsException(needs);
        }
    }
    public double getBalance() {
        return balance;
    }
}
// Main class to demonstrate
public class BankDemo {
    public static void main(String[] args) {
        CheckingAccount account = new CheckingAccount(101);
        account.deposit(500);
        try {
            account.withdraw(100);
            account.withdraw(600); // This will cause the InsufficientFundsException
            System.out.println("Transaction completed."); // This won't be executed
        } catch (InsufficientFundsException e) {
            System.out.println("Caught Exception: " + e.getMessage());
            e.printStackTrace(); // Prints the stack trace for debugging
        } finally {
            System.out.println("Finally block executed. Current balance: $" + account.getBalance());
        }
        System.out.println("Program continues after exception handling.");
    }
}

Output:

Deposited $500.0. New balance: $500.0
Withdrew $100.0. New balance: $400.0
Caught Exception: You are short $200.0
InsufficientFundsException: You are short $200.0
        at CheckingAccount.withdraw(BankDemo.java:44)
        at BankDemo.main(BankDemo.java:65)
Finally block executed. Current balance: $400.0
Program continues after exception handling.

In this example, the withdraw method explicitly declares that it throws InsufficientFundsException. The main method then uses a try-catch-finally block to handle this checked exception. For cleanup actions, the finally block is perfect because it is guaranteed to run whether or not an exception arises.

Index