In Java inheritance, method overriding is the cornerstone of polymorphism. When a subclass overrides a superclass method, it must maintain a consistent external interface so that client code can interact with both parent and child objects interchangeably.

This design contract is strictly enforced by the Java compiler, which monitors parameter counts, types, return types, and most importantly, the exceptions declared in the throws clause. If subclass methods were allowed to throw arbitrary checked exceptions, it would break compile-time type safety. A developer calling the parent class method would have no way of knowing they need to wrap their code in a try-catch block to handle a surprise exception thrown by a child instance. In this guide, we will analyze the strict rules Java imposes on checked and unchecked exceptions during method overriding.

Real-World Analogy: The Subcontractor's Agreement

To visualize this compiler rule, imagine a business contract between a parent contractor (the superclass) and a client:

  • The parent contractor signs a deal to construct a house. The contract warns the client of minor, routine delays that are easy to anticipate and fix (representing unchecked exceptions, like NullPointerException).
  • Later, the parent contractor delegates the actual building phase to a subcontractor (the subclass method override).
  • Because the subcontractor is bound by the original contract, they cannot suddenly introduce high-risk, major delays (representing checked exceptions, like a 3-year import permit delay IOException) that were never declared in the original contract.
The subcontractor is allowed to make stricter promises (such as declaring no delays or subclassing existing risks), but they can never introduce new, broader liabilities to the client.

Rule 1: Superclass Method Declares No Exceptions

If the parent class method does not declare any exceptions in its signature:

  • The overridden child method cannot declare any checked exceptions (like IOException or SQLException). If you attempt to do so, the compiler will fail to build the project.
  • The child method is allowed to declare unchecked exceptions (like ArithmeticException or IllegalArgumentException). Unchecked exceptions represent programming bugs rather than recoverable runtime states, so they do not violate the compiler's strict API contract.

Rule 2: Superclass Method Declares Checked Exceptions

If the parent class method declares a checked exception:

  • The overridden child method can declare the same checked exception.
  • The child method can declare a subclass of the parent's exception (which is a narrower, more specific checked exception).
  • The child method can choose to declare no exceptions at all, fulfilling the contract without throwing any errors.
  • The child method cannot throw a broader parent exception or a completely unrelated checked exception. For example, if the parent throws IOException, the child cannot throw Exception or SQLException.

Java Implementation Code

Here is a complete Java implementation demonstrating compile-time rules for method overrides declaring unchecked exceptions:

class OverridenClass {
    OverridenClass msg() { // Declares no exceptions
        System.out.println("parent");
        return this;
    }
}
 
public class OverridingExample extends OverridenClass {
    // Overridden method - OK to declare unchecked NullPointerException!
    @Override
    OverridingExample msg() throws NullPointerException { 
        System.out.println("TestExceptionChild");
        return this;
    }
 
    public static void main(String args[]) {
        OverridenClass p = new OverridingExample();
        p.msg(); // Triggers the child overridden method
    }
}

Conclusion & Design Benefits

By enforcing these exception propagation rules, Java maintains the Liskov Substitution Principle (LSP). It ensures that client code written to consume a superclass can safely run against any child subclass instance without compiling new, unexpected exception handler logic. This design guarantee is essential for building robust distributed systems.