00:00

Method Overriding in Java – Rules and Examples

Method overriding happens when a subclass provides its own implementation of an instance method it inherits from a superclass or interface, using the same name, parameters and a compatible return type. When the method is called, the JVM runs the version belonging to the actual object. This is Java's runtime polymorphism.

This tutorial covers how overriding works, the @Override annotation, calling the parent version with super, every overriding rule (return types, access, exceptions), and why static, private and final methods cannot be overridden.

1. How Overriding Works

OverridingBasics.java
public class OverridingBasics {
    static class Notification {
        String channel() { return "generic"; }
        void send(String msg) { System.out.println("[" + channel() + "] " + msg); }
    }

    static class EmailNotification extends Notification {
        @Override String channel() { return "email"; }        // overrides
    }

    static class SmsNotification extends Notification {
        @Override String channel() { return "sms"; }
        @Override void send(String msg) {                       // overrides and extends
            String shortMsg = msg.length() > 20 ? msg.substring(0, 20) + "..." : msg;
            super.send(shortMsg);                               // reuse the parent version
        }
    }

    public static void main(String[] args) {
        Notification[] all = { new Notification(), new EmailNotification(), new SmsNotification() };
        for (Notification n : all) n.send("Your order #1042 has been shipped today");
    }
}
Output
[generic] Your order #1042 has been shipped today
[email] Your order #1042 has been shipped today
[sms] Your order #1042 has...
  • EmailNotification overrides only channel(); the inherited send() calls it and gets the email version.
  • SmsNotification overrides send() and reuses the parent logic with super.send(...).
  • The array is typed Notification[], yet each element behaves according to its real class. This is dynamic method dispatch.

2. Always Use @Override

@Override asks the compiler to confirm that the method really overrides something. Without it, a small mistake creates an overload instead, and the bug is silent:

OverrideAnnotation.java
public class OverrideAnnotation {
    static class Point {
        final int x, y;
        Point(int x, int y) { this.x = x; this.y = y; }

        // Mistake: parameter type is Point, not Object -> this OVERLOADS equals, it does not override it.
        // Adding @Override here would make the compiler report the mistake.
        public boolean equals(Point other) { return x == other.x && y == other.y; }
    }

    static class FixedPoint {
        final int x, y;
        FixedPoint(int x, int y) { this.x = x; this.y = y; }

        @Override
        public boolean equals(Object o) {
            return o instanceof FixedPoint p && x == p.x && y == p.y;
        }
        @Override
        public int hashCode() { return 31 * x + y; }
    }

    public static void main(String[] args) {
        Object a = new Point(1, 2), b = new Point(1, 2);
        System.out.println("Point equals (wrong overload):  " + a.equals(b));

        Object c = new FixedPoint(1, 2), d = new FixedPoint(1, 2);
        System.out.println("FixedPoint equals (overridden): " + c.equals(d));
    }
}
Output
Point equals (wrong overload):  false
FixedPoint equals (overridden): true

With @Override on equals(Point), compilation would fail with "method does not override or implement a method from a supertype", pointing straight at the bug.

3. Rules of Method Overriding

RuleDetails
Same signatureSame method name and same parameter types in the same order
Return typeSame type, or a subtype for reference types (covariant return); primitives must match exactly
Access modifierSame or wider (protected → public is fine; public → protected is an error)
Checked exceptionsSame, narrower, fewer or none; never new or broader checked exceptions
Unchecked exceptionsAny, without restriction
InheritanceOnly inherited instance methods can be overridden (requires IS-A relationship)
Not overridablestatic, private and final methods, and constructors
OverridingRules.java
import java.io.*;

public class OverridingRules {
    static class Animal {
        protected Animal reproduce() { return new Animal(); }
        void load(String file) throws IOException { }
        public String toString() { return "Animal"; }
    }

    static class Cat extends Animal {
        // Covariant return type (Cat is a subtype of Animal) and wider access (protected -> public)
        @Override public Cat reproduce() { return new Cat(); }

        // May throw fewer or narrower checked exceptions (FileNotFoundException extends IOException)
        @Override void load(String file) throws FileNotFoundException {
            throw new FileNotFoundException(file + " not found");
        }
        public String toString() { return "Cat"; }
    }

    public static void main(String[] args) {
        Animal a = new Cat();
        System.out.println("reproduce() returned: " + a.reproduce());
        try { a.load("cat.json"); }
        catch (IOException e) { System.out.println("Caught: " + e); }
    }
}
Output
reproduce() returned: Cat
Caught: java.io.FileNotFoundException: cat.json not found

4. Static, Private and Final Methods

Static methods are hidden, not overridden

Static methods cannot be overridden in Java, only hidden

A static method with the same signature in a subclass hides the parent's method. Which one runs depends on the reference type at compile time, not the object.

Private methods are not inherited

Private methods cannot be overridden in Java

A method in the subclass with the same name as a parent's private method is a completely new method. Parent code calling its private method always gets its own version.

StaticHiding.java
public class StaticHiding {
    static class Parent {
        static String info() { return "Parent.info (static)"; }
        private String secret() { return "Parent.secret (private)"; }
        String callSecret() { return secret(); }
    }
    static class Child extends Parent {
        static String info() { return "Child.info (static)"; }      // hides, not overrides
        private String secret() { return "Child.secret (private)"; } // new method, not an override
    }

    public static void main(String[] args) {
        Parent p = new Child();
        System.out.println(Parent.info());   // static: resolved by type at compile time
        System.out.println(Child.info());
        System.out.println(p.callSecret());  // private: parent's version, no dynamic dispatch
    }
}
Output
Parent.info (static)
Child.info (static)
Parent.secret (private)

Final methods are locked

Final methods cannot be overridden in Java

Declaring a method final prevents overriding; attempting it is a compile error ("overridden method is final"). See final keyword.

5. Calling the Parent Version with super

Inside an overriding method, super.methodName(...) calls the parent's implementation. This is the standard way to extend rather than replace behaviour, for example adding fields to toString() or extra checks before saving. See super keyword.

6. Overriding Object Methods

Every class inherits from Object, and these methods are commonly overridden:

  • toString(): a readable description of the object.
  • equals(Object) and hashCode(): value equality; always override them together so objects work correctly in HashMap and HashSet.
  • clone(): rarely; copy constructors are usually preferred.

Records generate correct equals, hashCode and toString automatically.

7. Best Practices

  • Annotate every override with @Override.
  • Honour the parent's contract (Liskov substitution principle): an override must work wherever the parent is expected.
  • Do not call overridable methods from constructors; the subclass override may run before the subclass's fields are initialised.
  • Make methods final, or classes final or sealed, when subclasses must not change behaviour.

8. Summary

  • Overriding replaces an inherited instance method with a subclass version; the object's class decides which runs.
  • Same name and parameters; covariant return types allowed; access can only widen; checked exceptions can only narrow.
  • Static methods are hidden, private methods are not inherited, and final methods cannot be overridden.
  • Use @Override and super.method() to write safe, extendable overrides.

Related Tutorials