Rules of Method Overriding

Naveen AnamaneniUpdated · 6 min read

An overriding method makes a promise: anywhere the parent's method could be called, the child's version can be called instead, safely. Every rule of overriding follows from that one promise. The child may do more for the caller (accept a wider audience, return something more specific, throw fewer problems), but never less. Interviewers ask for this list constantly, so learn it through the reason, not by rote.

The Rules at a Glance

What an override may change, and what it may not
Part of the methodRule for the overriding method
Name and parameter listMust be exactly the same (otherwise it's an overload)
Return typeSame type, or a subtype for reference types (covariant return). Primitives must match exactly
Access modifierSame or wider: protected can become public; public can't become protected
Checked exceptionsNone, the same, or narrower ones. No new or broader checked exceptions
Unchecked exceptionsAny; they're not checked by the compiler
final methodsCan't be overridden
static methodsCan't be overridden; a same-signature static method in the child hides the parent's
private methodsCan't be overridden; they aren't visible to the child
ConstructorsCan't be overridden; they aren't inherited

Breaking the Rules

Each method in SalesReport below breaks one rule:

Rules.java
class Report {
    public String title() { return "Report"; }
    String load() throws java.io.IOException { return ""; }
    final String id() { return "R1"; }
    int pages() { return 1; }
}
class SalesReport extends Report {
    String title() { return "Sales"; }
    String load() throws Exception { return ""; }
    String id() { return "S1"; }
    long pages() { return 2; }
}
Compiler output
Rules.java:8: error: title() in SalesReport cannot override title() in Report
    String title() { return "Sales"; }
           ^
  attempting to assign weaker access privileges; was public
Rules.java:9: error: load() in SalesReport cannot override load() in Report
    String load() throws Exception { return ""; }
           ^
  overridden method does not throw Exception
Rules.java:10: error: id() in SalesReport cannot override id() in Report
    String id() { return "S1"; }
           ^
  overridden method is final
Rules.java:11: error: pages() in SalesReport cannot override pages() in Report
    long pages() { return 2; }
         ^
  return type long is not compatible with int
4 errors

Why each rule exists

  1. Weaker access (line 8): code that calls report.title() from another package relies on it being public. If the child made it package-private, that call would break for SalesReport objects.
  2. Broader exception (line 9): callers of load() were told to handle only IOException. A child throwing any Exception would surprise them.
  3. Final (line 10): final means "this behavior is fixed"; the parent's author forbade overriding.
  4. Return type (line 11): callers expect an int. A long doesn't fit, and primitive return types have no subtypes.

What an Override May Do

Allowed.java▶ Try it
import java.io.FileNotFoundException;
import java.io.IOException;
 
public class Allowed {
    static class Report {
        protected Object build() throws IOException {
            return "report";
        }
    }
 
    static class SalesReport extends Report {
        @Override
        public String build() throws FileNotFoundException {
            return "Sales report by Advitha";
        }
    }
 
    public static void main(String[] args) throws IOException {
        Report r = new SalesReport();
        System.out.println(r.build());
        System.out.println(new SalesReport().build().length());
    }
}
Output
Sales report by Advitha
23

How it works

  1. Access widened: protected became public. Everyone who could call it before still can.
  2. Return type narrowed: Object became String (a covariant return type, its own lesson). Through a SalesReport variable, build() returns a String, so .length() works without a cast.
  3. Exception narrowed: FileNotFoundException is a subclass of IOException, so callers who handle IOException are still covered.

Common Mistakes

Reducing access when overriding. Interface methods are implicitly public, so their implementations must be public too; leaving out public is a very common compile error.

Adding a checked exception in the override. Wrap it in an unchecked exception or handle it inside the method.

Thinking a same-signature static method overrides. It hides; see "Can Static, Private and Final Methods Be Overridden".

Interview Corner

What are the rules of method overriding in Java?

The overriding method must have the same name and parameter list. Its return type must be the same or a subtype (covariant), and primitives must match exactly. Its access must be the same or wider. It may throw fewer, narrower or no checked exceptions, but not new or broader ones; unchecked exceptions are unrestricted. Final, static and private methods, and constructors, can't be overridden.

Can an overriding method throw an exception the parent method doesn't throw?

Only if it's unchecked, such as IllegalStateException. A new or broader checked exception isn't allowed, because callers written against the parent method wouldn't be prepared to handle it.

Practice

  1. Easy: For each change to protected Number total() throws IOException, say allowed or not: public Integer total(); Number total(); protected Number total() throws Exception; protected Number total().
  2. Harder: Write an interface Greeter with String greet(String name), implement it without the word public, read the compile error, then fix it.

Quick check: Overriding rules

4 questions
1The parent method is protected. Which access can the override use?
2The parent returns Object. Which return type can the override use?
3The parent throws IOException. Which may the override throw?
4Which of these can be overridden?

Key Takeaways

Was this tutorial helpful?

Lesson 124 of 482