Exceptions ·
‹ Previous Next ›
⏱ 5 min read Modified: 2026-10-30

Exception Handling with Method Overriding and Constructors in Java

You write class FileTask implements Runnable, read a file inside run(), add throws IOException to the method — and the code stops compiling: run() in FileTask cannot implement run() in Runnable; overridden method does not throw IOException. The compiler is not being picky: this is the rule of exception handling with method overriding at work.

Exception handling with method overriding in Java follows two rules for checked exceptions. A method that overrides a superclass or interface method cannot declare in throws checked exceptions that are broader than, or unrelated to, the exceptions of the overridden method. A subclass constructor, on the contrary, must declare every checked exception of the superclass constructor it invokes (or a superclass of it). Unchecked exceptions are not restricted by either rule.

Two rules of exception handling with inheritance

With inheritance, the compiler checks the throws clause of overriding methods and of subclass constructors. An overriding method may declare the same checked exceptions as the superclass method, their subclasses, or nothing at all. A subclass constructor must declare all checked exceptions of the superclass constructor it calls, or their superclasses.

  • Method overriding. An overriding method in a subclass cannot throw checked exceptions that are higher in the hierarchy than the exceptions of the superclass method, nor new checked exceptions unrelated to them.
  • Constructor. A subclass constructor must include in its throws clause every exception class (or a superclass of it) from the throws clause of the superclass constructor that it invokes when the object is created.

What checked and unchecked exceptions are is covered in Checked vs unchecked exceptions in Java, and the declaration syntax in The throws keyword and checked exceptions. Both rules in one example:

import java.io.IOException;

public class SuperClass {
    public SuperClass() throws IOException {
    }

    public void start() throws IOException {
    }
}
import java.io.FileNotFoundException;
import java.io.IOException;

public class SubClass extends SuperClass {
    // the constructor must declare IOException from the SuperClass constructor;
    // ArithmeticException is unchecked and can be listed freely
    public SubClass() throws IOException, ArithmeticException {
    }

    // FileNotFoundException is a subclass of IOException - narrowing is allowed
    @Override
    public void start() throws FileNotFoundException {
    }
}

Note the asymmetry: an overriding method can narrow the list of exceptions, while a subclass constructor can only keep or extend it.

Overriding method throws exception: the list cannot grow

An overriding method in Java can declare in throws only the checked exceptions declared by the superclass method or their subclasses; it can drop throws entirely and can add any unchecked exceptions. A broader checked type (Exception) or an unrelated one (SQLException) is a compile-time error.

Options for the superclass method public void start() throws IOException:

throws in the subclass method Compiles? Why
throws IOException Yes The same type
throws FileNotFoundException, EOFException Yes Both are subclasses of IOException
no throws Yes The method promises less than the parent
throws IllegalArgumentException Yes Unchecked exception, the rule does not apply
throws Exception No A superclass of IOException — broader in the hierarchy
throws SQLException No A new checked exception unrelated to IOException

If you try to declare throws Exception, javac reports:

error: start() in SubClass cannot override start() in SuperClass
    public void start() throws Exception {
                ^
  overridden method does not throw Exception

Why this rule exists

The rule protects polymorphic code. The method below works with any subclass of SuperClass and, judging by the signature of start(), catches only IOException:

static void launch(SuperClass obj) {
    try {
        obj.start();
    } catch (IOException e) {
        System.out.println("Handled: " + e);
    }
}

If a subclass could throw SQLException from start(), it would slip past this catch, even though the compiler guaranteed that all checked exceptions were handled. That is why a subclass may promise less than its parent, but never more.

The compiler looks at the reference type, not at the object

The compiler decides which exceptions must be handled by the declared type of the variable. The same object requires try-catch when called through a superclass reference and does not require it through a reference to the subclass that dropped throws:

import java.io.IOException;

class Parser {
    void parse(String s) throws IOException {
    }
}

class StrictParser extends Parser {
    @Override
    void parse(String s) { // throws removed - this is allowed
        System.out.println("Parsing: " + s);
    }
}

public class StaticTypeDemo {
    public static void main(String[] args) {
        StrictParser strict = new StrictParser();
        strict.parse("a");          // compiles without try-catch

        Parser p = strict;          // the same object
        try {
            p.parse("b");           // but here catch IOException is required
        } catch (IOException e) {
            System.out.println("Never happens");
        }
    }
}
Parsing: a
Parsing: b

Remember

Checking throws is the job of the compiler, not the JVM. At run time the method of the actual object is called, but the decision «is a catch needed here» is made in advance, by the reference type. That is why narrowing throws in a subclass is useful: code that works with the concrete class gets rid of unnecessary try-catch blocks.

Implementing interfaces: Runnable, AutoCloseable and two interfaces at once

The overriding rule also applies when a class implements interface methods: the class method cannot declare a checked exception that is missing from the throws clause of the interface method. This explains a common situation in practice: you cannot declare IOException in Runnable.run(), Comparator.compare() or Object.toString(), because the original methods declare nothing.

A checked exception inside run()

If an exception cannot be declared, handle it on the spot or wrap it in an unchecked exception, keeping the cause. For I/O errors the JDK has a ready-made class, UncheckedIOException (Java 8+). The example uses Files.readString and Path.of, which require Java 11+:

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;

public class FileTask implements Runnable {
    private final Path path;

    public FileTask(Path path) {
        this.path = path;
    }

    @Override
    public void run() {             // throws IOException here is a compile-time error
        try {
            System.out.println(Files.readString(path));
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    public static void main(String[] args) {
        new FileTask(Path.of("missing.txt")).run();
    }
}

Output if the file does not exist (fragment):

Exception in thread "main" java.io.UncheckedIOException: java.nio.file.NoSuchFileException: missing.txt
	at FileTask.run(FileTask.java:18)
	at FileTask.main(FileTask.java:23)
Caused by: java.nio.file.NoSuchFileException: missing.txt
	...

Narrowing throws in AutoCloseable.close()

The method AutoCloseable.close() is declared as throws Exception. If your resource throws no checked exceptions when closed, drop throws in the implementation — and try-with-resources with this class no longer requires catch (Exception e):

public class DbSession implements AutoCloseable {
    @Override
    public void close() {           // no throws Exception
        System.out.println("Session closed");
    }

    public static void main(String[] args) {
        try (DbSession s = new DbSession()) {
            System.out.println("Working");
        }                           // no catch needed
    }
}
Working
Session closed

If DbSession kept the signature close() throws Exception, the same main would not compile: unreported exception Exception; must be caught or declared to be thrown.

Will it compile: one method from two interfaces

A tricky case: a class implements two interfaces with the same method but different throws clauses. The class method overrides both at once, so it can declare only the exceptions allowed by both interfaces:

import java.io.IOException;
import java.sql.SQLException;

interface FileSource {
    void open() throws IOException;
}

interface DbSource {
    void open() throws SQLException;
}

class HybridSource implements FileSource, DbSource {
    @Override
    public void open() throws IOException { // compile-time error
    }
}
error: open() in HybridSource cannot implement open() in DbSource
  overridden method does not throw IOException

IOException and SQLException are not related by inheritance and have no common allowed checked type, so HybridSource.open() can declare only unchecked exceptions or nothing at all.

Subclass constructor and superclass constructor exceptions

A subclass constructor always calls a superclass constructor — explicitly with super(...) or implicitly, so every checked exception of that constructor becomes an exception of the subclass constructor. If the superclass constructor throws an exception, the subclass constructor must declare it in throws with the same type or a superclass; it may also add new checked exceptions.

Constructors are not inherited and not overridden: the calling code writes new SubClass() and sees the throws clause of exactly this constructor. So the «no more than the parent» restriction is not needed here, and the rule works in the opposite direction.

Options for the superclass constructor public SuperClass() throws IOException:

Subclass constructor Compiles? Why
SubClass() throws IOException Yes The same type
SubClass() throws Exception Yes A superclass of IOException covers it
SubClass() throws IOException, SQLException Yes New checked exceptions may be added
SubClass() throws FileNotFoundException No A subclass does not cover every IOException
SubClass() without throws No unreported exception IOException; must be caught or declared to be thrown
No constructor written No The default constructor declares no exceptions: unreported exception IOException in default constructor

You cannot catch an exception of the superclass constructor inside the subclass constructor: the super(...) call cannot be placed inside a try block. This holds in Java 25 as well, where other statements are allowed before super(...) (JEP 513, Flexible Constructor Bodies): the call itself remains a separate top-level statement. If the exception has to be converted, do it in a factory method that calls the constructor.

What happens to the object if a constructor throws an exception

If a constructor completes with an exception, the new expression returns no reference, and the variable keeps its previous value — for example, null. The object for which the JVM has already allocated memory is unreachable for the program and will be removed by the garbage collector. So after catching such an exception you must not use the variable without a check.

public class ConstructorException {
    private int i;

    public ConstructorException(int i) {
        this.i = 20 / i;            // ArithmeticException when i == 0
    }

    public int getI() {
        return i;
    }
}
public class ExceptionInConstructorExample {
    public static void main(String[] args) {
        ConstructorException p = null;
        try {
            p = new ConstructorException(0);
        } catch (ArithmeticException e) {
            System.out.println("The constructor threw: " + e.getMessage());
        }

        if (p != null) {
            System.out.println("i = " + p.getI());
        } else {
            System.out.println("Object not created, p == null");
        }
    }
}
The constructor threw: / by zero
Object not created, p == null

A common mistake is to swallow the constructor exception and call p.getI() right away without a check. The constructor exception is caught, but p is still null, and the program crashes anyway, now with a NullPointerException (since Java 15 the message is Cannot invoke "ConstructorException.getI()" because "<local1>" is null). Either check the reference or put all code that needs the object inside the same try block.

Frequently Asked Questions

Do the throws rules apply to static methods, which cannot be overridden?

Yes. A static method in a subclass with the same signature does not override but hides the superclass method, yet the Java Language Specification applies the same restriction to it: declaring a broader or a new checked exception is a compile-time error.

The subclass removed throws, so why does the compiler still require catch?

Because the call goes through a reference of the superclass or interface type, and the compiler checks exceptions by the declared type of the variable, not by the actual object. Declare the variable with the subclass type and try-catch is no longer needed. An example with output is in Overriding method throws exception.

How do I report an IOException from toString() or compare(), which declare no throws?

Wrap it in an unchecked exception and keep the cause: throw new UncheckedIOException(e) for IOException or throw new RuntimeException(e) for other checked exceptions. Do not swallow it with an empty catch: the error would disappear without a trace. The same technique works for lambdas — see the FAQ of throw and throws in Java.

Can a subclass constructor declare fewer exceptions if it catches them in its own code?

No, not when the exception is declared by the superclass constructor being called: super(...) cannot be placed inside try, so its exceptions always end up in the throws clause of the subclass constructor. The way out is to call another superclass constructor that does not declare them, or to create the object through a factory method that catches the exception itself.

Comments

Please log in or register to have a possibility to add comment.