Skip to content
Java polymorphism 6 min read

Method Overriding

Method overriding lets a subclass provide its own implementation of a method that is already defined in its parent class. When you call that method on a subclass object, Java runs the subclass version — even if the variable holding the object is typed as the parent. That runtime decision is the heart of runtime polymorphism.

Why Override a Method?

Inheritance gives you a starting point, but the parent’s behavior is rarely a perfect fit for every child. Overriding lets you keep the same method signature while tailoring the logic to the specific type.

Classic example: a Shape parent class has a draw() method, but a Circle and a Rectangle each need to draw themselves differently. You override draw() in each subclass instead of adding brand-new, differently named methods.

Basic Syntax

class Animal {
    void sound() {
        System.out.println("Some generic animal sound");
    }
}

class Dog extends Animal {
    @Override
    void sound() {
        System.out.println("Woof!");
    }
}

class Cat extends Animal {
    @Override
    void sound() {
        System.out.println("Meow!");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal a = new Dog();   // reference type: Animal, object type: Dog
        a.sound();

        a = new Cat();
        a.sound();
    }
}

Output:

Woof!
Meow!

The variable a is of type Animal, but Java checks the actual object at runtime and calls the correct sound() method. That is dynamic dispatch.

The @Override Annotation

The @Override annotation is optional but strongly recommended. It tells the compiler: “I intend to override a method from the parent.” If you accidentally misspell the method name or use the wrong parameter types, the compiler immediately reports an error instead of silently creating a new, unrelated method.

class Dog extends Animal {
    @Override
    void sond() {  // typo — compiler error: "method does not override"
        System.out.println("Woof!");
    }
}

Tip: Always use @Override. It costs nothing and catches bugs instantly.

Rules for Method Overriding

For a valid override, all of these must hold:

RuleDetail
Same method nameExactly the same name as the parent method
Same parameter listSame number, types, and order of parameters
Same or covariant return typeCan return a subtype of the parent’s return type (covariant return)
Access modifierCannot be more restrictive than the parent (can be equal or wider)
ExceptionCannot declare new or broader checked exceptions than the parent
Not staticStatic methods are hidden, not overridden (see below)
Not finalfinal methods cannot be overridden
Not privatePrivate methods are invisible to subclasses; they cannot be overridden

Access Modifier Example

class Parent {
    protected void display() {
        System.out.println("Parent");
    }
}

class Child extends Parent {
    @Override
    public void display() {   // widening protected → public is allowed
        System.out.println("Child");
    }
}

Narrowing the access (e.g., changing public to protected) causes a compile error.

Calling the Parent’s Version with super

Sometimes you want to extend, not replace, the parent behavior. Use the super keyword to invoke the parent’s method from inside the overriding method.

class Vehicle {
    void start() {
        System.out.println("Starting engine...");
    }
}

class ElectricCar extends Vehicle {
    @Override
    void start() {
        super.start();                          // runs Vehicle.start() first
        System.out.println("Activating battery management system.");
    }
}

public class Main {
    public static void main(String[] args) {
        new ElectricCar().start();
    }
}

Output:

Starting engine...
Activating battery management system.

Note: super.method() can only be called from an instance method — not from a static context.

Overriding vs. Hiding (Static Methods)

Static methods belong to the class, not to an instance, so they cannot be overridden in the true polymorphic sense. Declaring a static method with the same signature in a subclass hides the parent’s method — the resolved method depends on the reference type, not the object type.

class Parent {
    static void greet() { System.out.println("Hello from Parent"); }
}

class Child extends Parent {
    static void greet() { System.out.println("Hello from Child"); }
}

public class Main {
    public static void main(String[] args) {
        Parent p = new Child();
        p.greet();   // prints "Hello from Parent" — resolved at compile time
    }
}

Output:

Hello from Parent

This is method hiding, not overriding. There is no runtime dispatch for static calls.

Overriding and Exception Handling

An overriding method:

  • May throw fewer or narrower checked exceptions than the parent.
  • May not throw new or broader checked exceptions.
  • May throw any unchecked (runtime) exceptions freely.
class Base {
    void read() throws IOException { }
}

class Derived extends Base {
    @Override
    void read() throws FileNotFoundException { }  // OK — narrower checked exception

    // void read() throws Exception { }  // COMPILE ERROR — broader checked exception
}

Overriding and the final Keyword

Mark a method final in the parent class to prevent any subclass from overriding it. This is useful for security-sensitive or invariant logic.

class BankAccount {
    final double calculateInterest(double principal) {
        return principal * 0.05;
    }
}

class SavingsAccount extends BankAccount {
    // @Override  <-- compile error: cannot override final method
    // double calculateInterest(double principal) { ... }
}

Overriding Abstract Methods

When you extend an abstract class or implement an interface, you are required to override all abstract methods. Overriding here is not optional — it is the contract you must fulfill.

abstract class Shape {
    abstract double area();     // must be overridden in concrete subclass
}

class Circle extends Shape {
    double radius;

    Circle(double radius) { this.radius = radius; }

    @Override
    double area() {
        return Math.PI * radius * radius;
    }
}

public class Main {
    public static void main(String[] args) {
        Shape s = new Circle(5);
        System.out.printf("Area: %.2f%n", s.area());
    }
}

Output:

Area: 78.54

Under the Hood: vtable & Dynamic Dispatch

When the JVM loads a class, it builds a virtual method table (vtable) — an array of method pointers for every overridable method. Each class gets its own vtable. If a subclass overrides a method, the subclass vtable entry points to the new implementation; otherwise it points to the parent’s.

At a call site like a.sound():

  1. The JVM reads the object’s actual type from the object header.
  2. It looks up the vtable for that type.
  3. It jumps to the correct method pointer.

This lookup adds a tiny indirection cost compared to a direct call, but the JIT compiler inlines or devirtualizes most such calls once it gathers enough profiling data — so the runtime overhead is usually zero in hot code.

Note: private, static, and final methods bypass the vtable entirely; they use static dispatch (resolved at compile time), which is marginally faster and easier for the JIT to optimize.

For a deep dive, see vtable & Dynamic Dispatch.

Overloading vs. Overriding at a Glance

FeatureOverloadingOverriding
LocationSame classParent → subclass
Method nameSameSame
ParametersMust differMust be identical
Return typeCan differ freelySame or covariant
Resolved byCompiler (static)JVM at runtime (dynamic)
Polymorphism typeCompile-timeRuntime
@Override neededNoStrongly recommended

For a side-by-side comparison with code examples, see Overloading vs Overriding.

  • Runtime Polymorphism — how the JVM picks the right method at runtime using dynamic dispatch
  • Method Overloading — the compile-time counterpart to overriding, using different parameter lists
  • Inheritance — overriding only makes sense in an inheritance hierarchy
  • super Keyword — invoke the parent’s overridden method from within a subclass
  • Abstract Class — abstract methods require overriding in every concrete subclass
  • vtable & Dynamic Dispatch — the JVM mechanism that makes runtime polymorphism work
Last updated June 13, 2026
Was this helpful?