Method Override

Su Suriya Ravichandran Updated 14 Nov 2026
9 min read ·Lesson 57 of 133

Method overriding is when a subclass provides its own implementation of a method it inherited from a superclass (or of a method declared in an interface it implements). When the method is called on an object of the subclass, the subclass version runs, even if the call is made through a variable of the superclass type. Overriding is the mechanism behind runtime polymorphism.

Basic Example

public class Animal {
    public String sound() {
        return "...";
    }

    public void speak() {
        System.out.println(getClass().getSimpleName() + " says " + sound());
    }
}

public class Dog extends Animal {
    @Override
    public String sound() {
        return "Woof";
    }
}

public class Cat extends Animal {
    @Override
    public String sound() {
        return "Meow";
    }
}
List<Animal> animals = List.of(new Animal(), new Dog(), new Cat());
for (Animal a : animals) {
    a.speak();
}

Output:

Animal says ...
Dog says Woof
Cat says Meow

speak() is defined only in Animal. Inside it, the call to sound() runs the version belonging to the actual object: Dog's for a dog, Cat's for a cat. The parent class writes the general behavior once, and subclasses customize only the part that differs.

How the JVM Chooses: Dynamic Dispatch

Animal a = new Dog();
a.sound();     // "Woof"

The variable's declared type is Animal, but the object is a Dog. For overridden instance methods, the JVM looks at the object's class at runtime and runs the most specific implementation available, searching upward through the hierarchy until it finds one. This is called dynamic dispatch.

The declared type still matters at compile time: it decides which methods may be called at all. a.fetch() would not compile even if Dog defined fetch(), because not every Animal has it.

Rules for a Valid Override

An overriding method must have:

RuleDetail
Same name and parameter typesOtherwise it is an overload, not an override
Compatible return typeThe same type, or a subtype (a covariant return) for reference types; primitives must match exactly
Same or wider accessprotected may become public; public may not become protected
No new or broader checked exceptionsIt may throw fewer, narrower, or none

And the parent method must be:

  • Visible to the subclass (not private)
  • Not final
  • Not static (static methods are hidden, not overridden)

Covariant return types

An override may return a more specific type than the original:

class Shape {
    Shape copy() { return new Shape(); }
}

class Circle extends Shape {
    @Override
    Circle copy() { return new Circle(); }   // Circle is a subtype of Shape: allowed
}

Circle c = new Circle().copy();              // no cast needed

Access cannot be narrowed

class Base {
    public void run() { }
}

class Derived extends Base {
    @Override
    protected void run() { }    // compile error: attempting to assign weaker access privileges
}

Code holding a Base reference may call run() from anywhere. If a subclass could make it protected, some objects would reject a call the compiler had already approved.

Checked exceptions cannot be broadened

class Reader {
    String read() throws IOException { ... }
}

class SafeReader extends Reader {
    @Override
    String read() { ... }                          // allowed: throws nothing

    // String read() throws Exception { ... }      // compile error: Exception is broader than IOException
}

Callers of Reader.read() are prepared to handle IOException. An override that could throw some other checked exception would surprise them. Unchecked exceptions (RuntimeException and its subclasses) are not restricted by this rule.

The @Override Annotation

@Override tells the compiler: "this method is meant to override a method from a supertype." The compiler then verifies it. Without the annotation, small mistakes silently create a new method instead:

class Product {
    final String sku;
    Product(String sku) { this.sku = sku; }

    public boolean equals(Product other) {       // intended to override equals(Object)
        return sku.equals(other.sku);
    }
}

The parameter is Product, not Object, so this is an overload. Collections and Objects.equals call equals(Object), the inherited version that compares identity, and two products with the same SKU are treated as different. Adding @Override turns the bug into a compile error:

error: method does not override or implement a method from a supertype

Other mistakes @Override catches: misspelled names (tostring, hashcode), wrong parameter types, and a parent method that was renamed or removed later. Use it on every method intended to override.

Calling the Parent Version: super.method()

An override can call the method it replaces using super:

public class Account {
    protected double balance;

    public void withdraw(double amount) {
        if (amount > balance) throw new IllegalStateException("Insufficient funds");
        balance -= amount;
    }
}

public class FeeAccount extends Account {
    private static final double FEE = 10;

    @Override
    public void withdraw(double amount) {
        super.withdraw(amount + FEE);    // reuse the parent's checks and logic
    }
}

The override adds a fee but keeps the parent's insufficient-funds check. Extending the parent's behavior through super is usually safer than copying its code, which would drift out of sync when the parent changes.

Overriding Methods of Object

Every class inherits methods from java.lang.Object. Three are commonly overridden.

toString

public class Book {
    private final String title;
    private final String author;

    public Book(String title, String author) {
        this.title = title;
        this.author = author;
    }

    @Override
    public String toString() {
        return title + " by " + author;
    }
}

System.out.println(new Book("Clean Code", "Robert C. Martin"));
// Clean Code by Robert C. Martin

Without the override, the output would be something like Book@4e25154f.

equals and hashCode

equals defines when two objects are logically equal. hashCode must be consistent with it: equal objects must have equal hash codes. Override both or neither.

import java.util.Objects;

public class Book {
    private final String isbn;
    private final String title;

    public Book(String isbn, String title) {
        this.isbn = isbn;
        this.title = title;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Book other)) return false;
        return isbn.equals(other.isbn);            // books are equal if their ISBNs match
    }

    @Override
    public int hashCode() {
        return Objects.hash(isbn);                 // uses the same field as equals
    }
}
Set<Book> shelf = new HashSet<>();
shelf.add(new Book("978-0132350884", "Clean Code"));
shelf.add(new Book("978-0132350884", "Clean Code (2nd printing)"));
System.out.println(shelf.size());   // 1: recognized as the same book

If hashCode were not overridden, the two objects would almost certainly have different hash codes, HashSet would place them in different buckets, and the set would contain two "equal" books. Records generate correct equals, hashCode, and toString automatically.

What Cannot Be Overridden

MemberWhyWhat happens instead
private methodsNot visible to subclassesA same-named subclass method is an unrelated new method
static methodsBelong to the class, resolved at compile timeA same-signature subclass method hides the parent's
final methodsExplicitly lockedCompile error
ConstructorsNot inheritedEach class declares its own
FieldsNot polymorphicA same-named field hides the parent's
class Parent {
    static String id() { return "parent"; }
}
class Child extends Parent {
    static String id() { return "child"; }      // hides, does not override
}

Parent p = new Child();
System.out.println(p.id());   // "parent": static methods use the declared type

Overriding Interface and Abstract Methods

Implementing an abstract method or an interface method follows the same rules and also uses @Override:

public interface Greeter {
    String greet(String name);
}

public class FormalGreeter implements Greeter {
    @Override
    public String greet(String name) {
        return "Good morning, " + name + ".";
    }
}

Interface methods are implicitly public, so implementations must be public.

A default method in an interface can also be overridden by an implementing class, which replaces the interface's provided behavior.

Overriding Versus Overloading

OverridingOverloading
WhereSubclass replaces an inherited methodSame class (or subclass) adds a method with the same name
ParametersMust be the sameMust differ
Return typeSame or covariantCan differ freely
ChosenAt runtime, by the object's actual classAt compile time, by the arguments' declared types
PurposeSpecialize behavior for a subtypeOffer the same operation for different inputs
@OverrideShould be usedNot applicable

Designing for Overriding

Overriding is powerful, but every overridable method becomes an extension point that subclasses may depend on.

  • Decide which methods are meant to be overridden, document what an override must do, and make the rest final or private.
  • Do not call overridable methods from constructors. The subclass override would run before the subclass's fields are initialized.
  • Respect the parent's contract. An override should behave as callers of the parent type expect. A Square that overrides setWidth to also change the height breaks code written for Rectangle, even though it compiles. This principle (subtypes must be usable wherever their parent type is expected) is known as the Liskov Substitution Principle.

Practical Example: Notification Templates

import java.util.List;

public class Notifications {
    static abstract class Message {
        protected final String user;

        Message(String user) { this.user = user; }

        String subject() { return "Notification"; }      // default; may be overridden
        abstract String body();                          // must be overridden

        final String render() {                          // fixed format; cannot be overridden
            return "[" + subject() + "]\nHi " + user + ",\n" + body();
        }

        @Override
        public String toString() { return getClass().getSimpleName() + " for " + user; }
    }

    static class Welcome extends Message {
        Welcome(String user) { super(user); }

        @Override String subject() { return "Welcome to GOCOURSE"; }
        @Override String body() { return "Your account is ready. Start with Java Basics!"; }
    }

    static class Reminder extends Message {
        private final String course;
        Reminder(String user, String course) { super(user); this.course = course; }

        @Override String body() { return "You have an unfinished lesson in " + course + "."; }
    }

    static class UrgentReminder extends Reminder {
        UrgentReminder(String user, String course) { super(user, course); }

        @Override String subject() { return "Action needed"; }
        @Override String body() { return super.body() + " Your access expires in 2 days."; }
    }

    public static void main(String[] args) {
        List<Message> outbox = List.of(
            new Welcome("Ada"),
            new Reminder("Linus", "Docker"),
            new UrgentReminder("Grace", "Django")
        );
        for (Message m : outbox) {
            System.out.println(m);
            System.out.println(m.render());
            System.out.println();
        }
    }
}

Output:

Welcome for Ada
[Welcome to GOCOURSE]
Hi Ada,
Your account is ready. Start with Java Basics!

Reminder for Linus
[Notification]
Hi Linus,
You have an unfinished lesson in Docker.

UrgentReminder for Grace
[Action needed]
Hi Grace,
You have an unfinished lesson in Django. Your access expires in 2 days.

render() is final, so every message has the same layout. body() is abstract, so each type must supply it. subject() has a default that Welcome and UrgentReminder override and Reminder keeps. UrgentReminder extends Reminder and overrides body() by calling super.body() and appending a warning, so the reminder text is written once. toString() overrides Object's version and, through getClass(), reports the actual class of each message. The loop treats every item as a Message, and dynamic dispatch selects the right subject and body for each.

0 Comments

Reviewed before they appear

No comments yet.

Java
Java Introduction Java Features Java Syntax Java Comments Java Statements Java Output Java Variables Java Data Types Java Type Casting Java Operators Java Strings Java Numbers Java Math Java Booleans Java User Input Java Conditions Java If Else Java Switch Java While Loop Java For Loop Java Break Java Continue Java Arrays Java Methods Java Parameters Java Return Values Java Scope Java Recursion Install Java Install JDK Java IDEs Java JDK Java JRE Java JVM Java Compiler Java File Compile Java Run Java JShell Basics Java Packages Java OOP Java Classes Java Objects Class Methods Constructors Constructor Overload this Keyword static Keyword final Keyword Encapsulation Inheritance Polymorphism Abstraction Access Modifiers super Keyword Method Overload Method Override Instanceof Operator Nested Classes Inner Classes Anonymous Classes Enums Records Array Basics Array Length Loop Arrays Multidimensional Arrays Array Copy Array Sorting Array Search ArrayList LinkedList HashSet TreeSet HashMap TreeMap Queue Interface Deque Interface Stack Class Iterator List Interface Set Interface Map Interface Collections Class Comparable Comparator Generics Basics Generic Classes Generic Methods Wildcards String Basics String Length String Methods String Concatenation String Compare String Search String Replace String Split String Format StringBuilder StringBuffer Text Blocks Regular Expressions Number Types Integer Class Float Class Double Class BigInteger BigDecimal Math Class Random Numbers Number Format Date Basics LocalDate LocalTime LocalDateTime ZonedDateTime Date Formatting Date Parsing Period Class Duration Class Java Instant Class Java Exception Basics Java try...catch Java Multiple Catch Blocks Java finally Block Java throw Keyword Java throws Keyword Java Checked Exceptions Java Unchecked Exceptions Java Custom Exceptions Java try-with-resources Java Exception Chaining
Ask about this post
AI Ask about this post

Ask questions about Method Override and get answers drawn from it.

Signed-in readers only.