Exception Handling with Method Overriding in Java

In the following sections, we will unravel the art of harmonizing method overriding and exception handling, shedding light on how these two facets can complement and enhance each other in the pursuit of creating more resilient, maintainable, and user-friendly software. So, let’s set sail on this enlightening journey, unravelling the intricacies that lie at the intersection of method overriding and exception handling.

Exception Handling with Method Overriding:

Exception handling with method overriding in object-oriented programming refers to the process of dealing with exceptions (errors or unexpected situations) that can occur when a subclass overrides a method from its parent (superclass) while maintaining proper error handling and propagation.

When a subclass overrides a method from its superclass, it can choose to either handle exceptions thrown by the overridden method or propagate them up the call chain. Proper exception handling helps ensure that the program behaves as expected and provides meaningful error messages or recovery mechanisms when errors occur.

Exception Handling with Method Overriding

Key points to consider

Exception Type Matching: When a subclass overrides a method, it must adhere to the same or more specific exceptions (subclasses) in its throws clause. In the example, the Car class method startEngine() is allowed to throw EngineStartException (same exception) or any subclass of it.

No Broader Exception: The subclass is not allowed to throw a broader (more general) exception than its superclass counterpart. For instance, you cannot throw an Exception if the superclass method throws EngineStartException.

Unchecked Exceptions: For unchecked exceptions (subclasses of RuntimeException), you’re not bound by the exception rules. You can freely throw unchecked exceptions in overridden methods, regardless of whether the superclass method throws checked exceptions.

Overriding without Exception: If the superclass method doesn’t declare any exceptions (including unchecked exceptions), the subclass is not required to declare any exceptions. It’s allowed to throw exceptions if needed, but it’s not mandatory.

Here are the Problems for exception handling with method overriding in Java:

Problem 1 – No Exception in Superclass:

Case 1: If the superclass method does not throw any exceptions (neither checked nor unchecked), the subclass method also cannot throw any exceptions. If the subclass method throw the checked exception, we will get a compile time error.

Here’s an example of Java code where the superclass method does not declare an exception, but the subclass overridden method declares a checked exception:

Example code:

class Superclass {
    void method() {
        System.out.println("Superclass method");
    }
}
class Subclass extends Superclass {
    @Override
    void method() throws CustomCheckedException {
        System.out.println("Subclass method");
        throw new CustomCheckedException("Checked Exception in Subclass");
    }
}
class CustomCheckedException extends Exception {
    CustomCheckedException(String message) {
        super(message);
    }
}
class DataFlair{
    public static void main(String[] args) {
        Superclass instance = new Subclass();

        try {
            instance.method();
        } catch (CustomCheckedException e) {
            System.out.println("Caught: " + e.getMessage());
        }
    }
}

Output:

Java: method() in com.company.Subclass cannot override method() in com.company.Superclass

overridden method does not throw com.company.CustomCheckedException

Explanation:

  • The Superclass defines a method called method() that does not declare any exceptions.
  • The Subclass overrides the method() and declares a CustomCheckedException, a checked exception.
  • The CustomCheckedException class is a checked exception that extends Exception.
  • In the main method, an instance of Subclass is created and invoked the method().

Case 2: If the superclass method does not throw any exceptions (neither checked nor unchecked),Subclass method can choose to throw unchecked exceptions.

Example Code:

class Superclass {
    void method() {
        System.out.println("Superclass method");
    }
}
class Subclass extends Superclass {
    @Override
    void method() {
        System.out.println("Subclass method");
    }
}
class DataFlair{
    public static void main(String[] args) {
        Superclass superClassInstance = new Superclass();
        Subclass subClassInstance = new Subclass();
        superClassInstance.method(); // Output: Superclass method
        subClassInstance.method();   // Output: Subclass method
    }
}

Output:

Superclass method
Subclass method

Explanation:

In this example, the Superclass defines a method called method() that doesn’t throw any exceptions. The Subclass overrides this method and also doesn’t throw any exceptions. The main method demonstrates how both the superclass and subclass methods can be invoked without any issues.

Problem 2 – Checked Exception in Superclass:

Case 1: If the superclass method throws a checked exception; the subclass method can:

a. Throw the same exception.

Example Code:

class InsufficientFundsException extends Exception {
    InsufficientFundsException(String message) {
        super(message);
    }
}
class BankAccount {
    protected double balance;
    BankAccount(double initialBalance) {
        this.balance = initialBalance;
    }
    void withdraw(double amount) throws InsufficientFundsException {
        if (amount > balance) {
            throw new InsufficientFundsException("Insufficient funds for withdrawal");
        }
        balance -= amount;
        System.out.println("Withdrawal successful. New balance: " + balance);
    }
}
class SavingsAccount extends BankAccount {
    SavingsAccount(double initialBalance) {
        super(initialBalance);
    }
    @Override
    void withdraw(double amount) throws InsufficientFundsException {
        try {
            super.withdraw(amount); // Call the superclass method
        } catch (InsufficientFundsException e) {
            System.out.println("SavingsAccount: " + e.getMessage());
            // Perform additional handling specific to SavingsAccount
        }
    }
}
class DataFlair{
    public static void main(String[] args) {
        BankAccount account1 = new SavingsAccount(1000);
        try {
            account1.withdraw(1500);
        } catch (InsufficientFundsException e) {
            System.out.println("Main: " + e.getMessage());
            // Perform general handling for all bank accounts
        }
    }
}

Output:

SavingsAccount: Insufficient funds for withdrawal

Explanation:

  • We have a BankAccount superclass with a method withdraw that can throw an InsufficientFundsException.
  • The SavingsAccount subclass inherits from BankAccount and overrides the withdraw method. It catches the exception thrown by the superclass method and provides additional handling specific to savings accounts.
  • In the DataFlair class, we create an instance of SavingsAccount and attempt to withdraw an amount greater than the balance.
  • The overridden withdraw method in the SavingsAccount class catches the exception, displays a message, and performs additional handling.
  • The main method also catches the exception and performs general handling for all bank accounts.

Case 2: If the superclass method throws a checked exception, the subclass method can:

b. Throw a subclass exception of the exception thrown by the superclass method.

Example code:

class ParentException extends Exception {
    ParentException(String message) {
        super(message);
    }
}
class ChildException extends ParentException {
    ChildException(String message) {
        super(message);
    }
}
class Superclass {
    void method() throws ParentException {
        System.out.println("Superclass method");
        throw new ParentException("Checked Exception in Superclass");
    }
}
class Subclass extends Superclass {
    @Override
    void method() throws ChildException {
        System.out.println("Subclass method");
        throw new ChildException("Subclass Exception in Subclass");
    }
}
class DataFlair{
    public static void main(String[] args) {
        Superclass instance = new Subclass();

        try {
            instance.method();
        } catch (ParentException e) {
            System.out.println("Caught: " + e.getMessage());
        }
    }
}

Output:

Subclass method

Caught: Subclass Exception in Subclass

Explanation:

  • The ParentException and ChildException classes represent checked exceptions, where ChildException is a subclass of ParentException.
  • The Superclass defines a method called method() that throws a ParentException.
  • The Subclass overrides the method() and throws a ChildException, which is a subclass of ParentException.
  • In the main method, we create an instance of Subclass and invoke the method(). We catch the ParentException (which includes its subclass ChildException) and print the caught message.

Case 3: If the Parentclass method throws a checked exception, the Childclass method can:

c. Choose not to throw any exception.

Example code:

class ParentException extends Exception {
    ParentException(String message) {
        super(message);
    }
}

class Superclass {
    void method() throws ParentException {
        System.out.println("Superclass method");
        throw new ParentException("Checked Exception in Superclass");
    }
}

class Subclass extends Superclass {
    @Override
    void method() {
        System.out.println("Subclass method");
        // No exception thrown in Subclass method
    }
}
class DataFlair{
    public static void main(String[] args) {
        Superclass instance = new Subclass();

        try {
            instance.method();
        } catch (ParentException e) {
            System.out.println("Caught: " + e.getMessage());
        }
    }
}

Output:

Subclass method

Explanation:

  • The ParentException class represents a checked exception.
  • The Superclass defines a method called method() that throws a ParentException.
  • The Subclass overrides the method() and chooses not to throw any exception.
  • In the main method, we create an instance of Subclass and invoke the method(). Since the Subclass method does not throw an exception, no exception is caught in the catch block.

Case 4: If the superclass method throws a checked exception, the subclass method can:

d. Cannot throw a parent exception.

Example code:

class ParentException extends Exception {
    ParentException(String message) {
        super(message);
    }
}

class Superclass {
    void method() throws ParentException {
        System.out.println("Superclass method");
        throw new ParentException("Checked Exception in Superclass");
    }
}

class Subclass extends Superclass {
    @Override
    void method() throws Exception {
        System.out.println("Subclass method");
        // Cannot throw ParentException directly here
        throw new Exception("Exception in Subclass"); // A subclass of Exception
    }
}
class DataFlair{
    public static void main(String[] args) {
        Superclass instance = new Subclass();

        try {
            instance.method();
        } catch (ParentException e) {
            System.out.println("Caught: " + e.getMessage());
        }
    }
}

Output:

java: method() in com.company.Subclass cannot override method() in com.company.Superclass overridden method does not throw java.lang.Exception

Explanation:

In this example, the Subclass attempts to throw a parent exception Exception in its overridden method(). This will result in a compilation error because it is not allowed to throw a parent exception. The Superclass method declares ParentException, and any exception thrown by the Subclass must follow the hierarchy rules.

Problem 3 – Unchecked Exception in Superclass:

If the superclass method throws an unchecked exception (subclass of RuntimeException), the subclass method can choose to throw the same exception or any other unchecked exception, including subclasses of RuntimeException.

Example Code:

class ParentUncheckedException extends RuntimeException {
    ParentUncheckedException(String message) {
        super(message);
    }
}
class Superclass {
    void method() {
        System.out.println("Superclass method");
        throw new ParentUncheckedException("Unchecked Exception in Superclass");
    }
}

class ChildUncheckedException extends ParentUncheckedException {
    ChildUncheckedException(String message) {
        super(message);
    }
}
class Subclass extends Superclass {
    @Override
    void method() {
        System.out.println("Subclass method");
        throw new ChildUncheckedException("Unchecked Exception in Subclass");
    }
}
class DataFlair{
    public static void main(String[] args) {
        Superclass superClassInstance = new Subclass();

        try {
            superClassInstance.method();
        } catch (ParentUncheckedException e) {
            System.out.println("Caught: " + e.getMessage());
        }
    }
}

Output:

Subclass method

Caught: Unchecked Exception in Subclass

Explanation:

  • The ParentUncheckedException and ChildUncheckedException classes extend RuntimeException, making them unchecked exceptions.
  • The Superclass defines a method called method() that throws ParentUncheckedException.
  • The Subclass overrides the method() and throws a ChildUncheckedException.
  • In the main method, we create an instance of Subclass and invoke the method(). We catch the unchecked exception and print the caught message.

Conclusion

When the SuperClass doesn’t specify any exceptions, the SubClass is limited to declaring only unchecked exceptions, excluding checked exceptions.

If the SuperClass specifies an exception, the SubClass is allowed to declare the same exception or its subclasses, along with new RuntimeExceptions. However, it’s restricted from introducing any new checked exceptions at the same level or higher.

In the case of the SuperClass specifying an exception, the SubClass can also choose not to declare any exceptions at all.